当前位置: 首页 > news >正文

SpringSecurity原理解析(二):认证流程

1、SpringSecurity认证流程包含哪几个子流程?

      1)账号验证

      2)密码验证

      3)记住我—>Cookie记录

      4)登录成功—>页面跳转

2、UsernamePasswordAuthenticationFilter

      在SpringSecurity中处理认证逻辑是在UsernamePasswordAuthenticationFilter这个过滤

      器中实现的,UsernamePasswordAuthenticationFilter 继承于 

      AbstractAuthenticationProcessingFilter 这个父类。

      当请求进来时,在doFilter 方法中会对请求进行拦截,判断请求是否需要认证,若不需要

      认证,则放行;否则执行认证逻辑;

              1

               

      注意:UsernamePasswordAuthenticationFilter 类中是没有 doFilter 方法的,doFilter

      方法是继承自父类 UsernamePasswordAuthenticationFilter 的。

            doFilter 方法代码如下:

//执行过滤的方法,所有请求都走这个方法
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {//请求和应答类型转换HttpServletRequest request = (HttpServletRequest)req;HttpServletResponse response = (HttpServletResponse)res;//判断请求是否需要认证处理,若不需要认证处理,则直接放行if (!this.requiresAuthentication(request, response)) {//放行,往下走chain.doFilter(request, response);} else {//执行到这里,进行认证处理if (this.logger.isDebugEnabled()) {this.logger.debug("Request is to process authentication");}Authentication authResult;try {//处理认证,然后返回 Authentication 认证对象//重点authResult = this.attemptAuthentication(request, response);//认证失败if (authResult == null) {return;}//认证成功之后注册sessionthis.sessionStrategy.onAuthentication(authResult, request, response);} catch (InternalAuthenticationServiceException var8) {this.logger.error("An internal error occurred while trying to authenticate the user.", var8);this.unsuccessfulAuthentication(request, response, var8);return;} catch (AuthenticationException var9) {this.unsuccessfulAuthentication(request, response, var9);return;}//if (this.continueChainBeforeSuccessfulAuthentication) {chain.doFilter(request, response);}//认证成功后的处理this.successfulAuthentication(request, response, chain, authResult);}}protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {if (this.logger.isDebugEnabled()) {this.logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);}//将认证成功后的对象保存到 SecurityContext中SecurityContextHolder.getContext().setAuthentication(authResult);//处理 remember-me属性this.rememberMeServices.loginSuccess(request, response, authResult);if (this.eventPublisher != null) {this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));}//认证成功后,页面跳转this.successHandler.onAuthenticationSuccess(request, response, authResult);}

              

      上边的核心代码是下边这一行:

             

       attemptAuthentication方法的作用是获取Authentication对象其实就是对应的认证过程,

       attemptAuthentication 方法在子类UsernamePasswordAuthenticationFilter 中实现的。

        attemptAuthentication 方法代码如下:

//认证逻辑
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {//如果我们设置了该认证请求只能以post方式提交,且当前请求不是post请求,表示当前请求不符合//认证要求,直接抛出异常,认证失败if (this.postOnly && !request.getMethod().equals("POST")) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());} else {//执行到这里表示开始执行认证逻辑//从请求中获取用户名和密码String username = this.obtainUsername(request);String password = this.obtainPassword(request);if (username == null) {username = "";}if (password == null) {password = "";}username = username.trim();//将用户名和密码包装成 UsernamePasswordAuthenticationToken  对象UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);//设置用户提交的信息到 UsernamePasswordAuthenticationToken  中this.setDetails(request, authRequest);//getAuthenticationManager():获取认证管理器//authenticate:真正处理认证的方法return this.getAuthenticationManager().authenticate(authRequest);}}

        1、

3、AuthenticationManager

     AuthenticationManager接口中就定义了一个方法authenticate方法,用于处理认证的请求;

     AuthenticationManager 接口定义如下:

public interface AuthenticationManager {//处理认证请求Authentication authenticate(Authentication authentication) throws AuthenticationException;}

    

      在这里AuthenticationManager的默认实现是ProviderManager.而在ProviderManager的

      authenticate方法中实现的操作是循环遍历成员变量List<AuthenticationProvider> providers

      。该providers中如果有一个AuthenticationProvider的supports函数返回true,那么就会调

      用该AuthenticationProvider的authenticate函数认证,如果认证成功则整个认证过程结束。

      如果不成功,则继续使用下一个合适的AuthenticationProvider进行认证,只要有一个认证

      成功则为认证成功。

      authenticate 方法定义如下:

             

//执行认证逻辑
public Authentication authenticate(Authentication authentication)throws AuthenticationException {//获取 Authentication 对象的类型Class<? extends Authentication> toTest = authentication.getClass();AuthenticationException lastException = null;AuthenticationException parentException = null;Authentication result = null;Authentication parentResult = null;boolean debug = logger.isDebugEnabled();//getProviders():获取系统支持的各种认证方式,如:QQ、微信、微博等等for (AuthenticationProvider provider : getProviders()) {//判断当前的 provider认证处理器 是否支持当前请求的认证类型,若不支持,则跳过if (!provider.supports(toTest)) {continue;}if (debug) {logger.debug("Authentication attempt using "+ provider.getClass().getName());}//执行到这里说明当前认证处理器支持当前请求的认证,try {//执行认证操作result = provider.authenticate(authentication);if (result != null) {copyDetails(authentication, result);break;}}catch (AccountStatusException e) {//。。。。。省略 。。。。。}catch (InternalAuthenticationServiceException e) {//。。。。。省略 。。。。。}catch (AuthenticationException e) {//。。。。。省略 。。。。。}}//如果循环结束后还没找到支持当前请求的认证处理器provider ,且父类不为空,则//尝试调用父类的认证方法进行认证处理if (result == null && parent != null) {// Allow the parent to try.try {result = parentResult = parent.authenticate(authentication);}catch (ProviderNotFoundException e) {//。。。。。省略 。。。。。}catch (AuthenticationException e) {//。。。。。省略 。。。。。}}//清空密码凭证if (result != null) {if (eraseCredentialsAfterAuthentication&& (result instanceof CredentialsContainer)) {// Authentication is complete. Remove credentials and other secret data// from authentication((CredentialsContainer) result).eraseCredentials();}//if (parentResult == null) {eventPublisher.publishAuthenticationSuccess(result);}return result;}// //异常处理if (lastException == null) {lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound",new Object[] { toTest.getName() },"No AuthenticationProvider found for {0}"));}//if (parentException == null) {prepareException(lastException, authentication);}throw lastException;}

       在上边的代码中,我们重点看的是下边这一行:

              result = provider.authenticate(authentication);

       因为是用户认证,所以这里authenticate方法走是AbstractUserDetailsAuthenticationProvider

       类中的实现,

      AbstractUserDetailsAuthenticationProvider.authenticate 方法定义如下所示:

//认证操作
public Authentication authenticate(Authentication authentication)throws AuthenticationException {Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,() -> messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports","Only UsernamePasswordAuthenticationToken is supported"));// 获取提交的账号String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED": authentication.getName();//标记,是否使用缓存,默认是使用的,先从缓存中查找提交的账号//若账号已经登录,则缓存中应该boolean cacheWasUsed = true;//根据账号名称从缓存中查找账号,若缓存中不存在该账号,则需要认证UserDetails user = this.userCache.getUserFromCache(username);if (user == null) {//若缓存中不存在该账号,没有缓存cacheWasUsed = false;try {//账号认证user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);}catch (UsernameNotFoundException notFound) {//。。。。。省略 。。。。。}Assert.notNull(user,"retrieveUser returned null - a violation of the interface contract");}try {//如果账号存在,即账号认证成功,则这里就开始密码认证//密码校验前的前置检查,检查账号是否过期、是否锁定等preAuthenticationChecks.check(user);//密码校验//user: 数据库中的数据//authentication: 表单提交的数据additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication);}catch (AuthenticationException exception) {//。。。。。省略 。。。。。}//检查凭证是否过期postAuthenticationChecks.check(user);//将用户保存到缓存中if (!cacheWasUsed) {this.userCache.putUserInCache(user);}Object principalToReturn = user;if (forcePrincipalAsString) {principalToReturn = user.getUsername();}return createSuccessAuthentication(principalToReturn, authentication, user);
}//创建具体的 Authentication 对象
protected Authentication createSuccessAuthentication(Object principal,Authentication authentication, UserDetails user) {// user.getAuthorities():返回用户的权限UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(),authoritiesMapper.mapAuthorities(user.getAuthorities()));result.setDetails(authentication.getDetails());return result;}

              密码前置校验如下图所示:

                      

      然后进入到retrieveUser方法中,retrieveUser和additionalAuthenticationChecks 方法

      具体的实现是DaoAuthenticationProvider 类中实现的,如下所示

@Overrideprotected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {prepareTimingAttackProtection();try {// getUserDetailsService会获取到我们自定义的UserServiceImpl对象,也就是会走我们自定义的认证方法了UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);if (loadedUser == null) {throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");}return loadedUser;}catch (UsernameNotFoundException ex) {//。。。。。省略 。。。。。}catch (InternalAuthenticationServiceException ex) {//。。。。。省略 。。。。。}catch (Exception ex) {//。。。。。省略 。。。。。}}//具体的密码校验逻辑
protected void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {//凭证为空(即密码没传进来),则直接抛出异常if (authentication.getCredentials() == null) {logger.debug("Authentication failed: no credentials provided");throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials","Bad credentials"));}//获取表单提交的密码String presentedPassword = authentication.getCredentials().toString();//拿表单提交的密码,与数据库中的密码进行匹配,若匹配失败,则抛出异常if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {logger.debug("Authentication failed: password does not match stored value");throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials","Bad credentials"));}}

http://www.lryc.cn/news/437321.html

相关文章:

  • 数据中台 | 数据资源管理平台介绍
  • 智慧环保平台建设方案
  • SpringMVC映射请求;SpringMVC返回值类型;SpringMVC参数绑定;
  • 【第28章】Spring Cloud之Sentinel注解支持
  • 鼎捷新一代PLM 荣膺维科杯 “2023年度行业优秀产品奖”
  • 如何升级用 Helm 安装的极狐GitLab Runner?
  • 08 vue3之认识bem架构及less sass 和scoped
  • 静态库的制作
  • PHP在现代Web开发中的高效应用与最佳实践
  • 大数据-134 - ClickHouse 集群三节点 安装配置启动
  • 2024网络安全人才实战能力白皮书安全测试评估篇
  • [项目][WebServer][解析错误处理]详细讲解
  • 51单片机应用开发---数码管的控制应用
  • Vue3+Django5+REST Framework开发电脑管理系统
  • Java8函数式接口全攻略
  • 英文软件汉化中文软件教程asi exe dll 等汉化教程
  • HTTP 请求方式`application/x-www-form-urlencoded` 与 `application/json` 怎么用?有什么区别?
  • prometheus 集成 grafana 保姆级别安装部署
  • Apache SeaTunnel Committer 进阶指南
  • 组件上的v-model(数据传递),props验证,自定义事件,计算属性
  • mfc140u.dll文件错误的相关修复方法,4种方法修复mfc140u.dll
  • Redis中使用布隆过滤器解决缓存穿透问题
  • css百分比布局中height:100%不起作用
  • java程序员入行科目一之CRUD轻松入门教程(二)
  • (不用互三)解密AI创作:提升Prompt提示词的提问技巧
  • Python 错误 ValueError 解析,实际错误实例详解 (二)
  • 会员计次卡渲染技术-—SAAS本地化及未来之窗行业应用跨平台架构
  • Redis网络模型、通信协议、内存回收
  • 闯关leetcode——21. Merge Two Sorted Lists
  • Notepad++中提升编码效率的关键快捷键