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;

               }

               //认证成功之后注册session

               this.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 类中实现的,如下所示


@Override

protected 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"));

 }

}