SpringSecurity认证流程详解

 更新时间:2018年05月25日 08:53:17   作者:whyalwaysmea  
这篇文章主要介绍了SpringSecurity认证流程详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

SpringSecurity基本原理

在之前的文章《SpringBoot + Spring Security 基本使用及个性化登录配置》中对SpringSecurity进行了简单的使用介绍,基本上都是对于接口的介绍以及功能的实现。 这一篇文章尝试从源码的角度来上对用户认证流程做一个简单的分析。
在具体分析之前,我们可以先看看SpringSecurity的大概原理:

SpringSecurity基本原理

其实比较简单,主要是通过一系列的Filter对请求进行拦截处理。

认证处理流程说明

我们直接来看UsernamePasswordAuthenticationFilter类,

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter
  // 登录请求认证
  public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    // 判断是否是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();
      // 生成Token,
      UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
      this.setDetails(request, authRequest);
      // 进一步验证
      return this.getAuthenticationManager().authenticate(authRequest);
    }
  }
}

attemptAuthentication方法中,主要是进行username和password请求值的获取,然后再生成一个UsernamePasswordAuthenticationToken 对象,进行进一步的验证。

不过我们可以先看看UsernamePasswordAuthenticationToken 的构造方法

public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
  // 设置空的权限
  super((Collection)null);
  this.principal = principal;
  this.credentials = credentials;
  // 设置是否通过了校验
  this.setAuthenticated(false);
}

其实UsernamePasswordAuthenticationToken是继承于Authentication,该对象在上一篇文章中有提到过,它是处理登录成功回调方法中的一个参数,里面包含了用户信息、请求信息等参数。

所以接下来我们看

this.getAuthenticationManager().authenticate(authRequest);

这里有一个AuthenticationManager,但是真正调用的是ProviderManager

public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
  public Authentication authenticate(Authentication authentication) throws AuthenticationException { 
    Class<? extends Authentication> toTest = authentication.getClass();
    AuthenticationException lastException = null;
    Authentication result = null;
    boolean debug = logger.isDebugEnabled();
    Iterator var6 = this.getProviders().iterator();

    while(var6.hasNext()) {
      AuthenticationProvider provider = (AuthenticationProvider)var6.next();
      // 1.判断是否有provider支持该Authentication
      if (provider.supports(toTest)) {
        // 2. 真正的逻辑判断
        result = provider.authenticate(authentication);
      }
  }
}

  1. 这里首先通过provider判断是否支持当前传入进来的Authentication,目前我们使用的是UsernamePasswordAuthenticationToken,因为除了帐号密码登录的方式,还会有其他的方式,比如SocialAuthenticationToken。
  2. 根据我们目前所使用的UsernamePasswordAuthenticationToken,provider对应的是DaoAuthenticationProvider。
public Authentication authenticate(Authentication authentication) throws AuthenticationException { 

  UserDetails user = this.userCache.getUserFromCache(username);
  if (user == null) {
    cacheWasUsed = false;
    // 1.去获取UserDetails
    user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
  }

  try {
    // 2.用户信息预检查
    this.preAuthenticationChecks.check(user);
    // 3.附加的检查(密码检查)
    this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
  } catch (AuthenticationException var7) {    
  }
  // 4.最后的检查
  this.postAuthenticationChecks.check(user);
  // 5.返回真正的经过认证的Authentication 
  return this.createSuccessAuthentication(principalToReturn, authentication, user);
}
  1. 去调用自己实现的UserDetailsService,返回UserDetails
  2. 对UserDetails的信息进行校验,主要是帐号是否被冻结,是否过期等
  3. 对密码进行检查,这里调用了PasswordEncoder
  4. 检查UserDetails是否可用。
  5. 返回经过认证的Authentication

这里的两次对UserDetails的检查,主要就是通过它的四个返回boolean类型的方法。
经过信息的校验之后,通过UsernamePasswordAuthenticationToken的构造方法,返回了一个经过认证的Authentication。

拿到经过认证的Authentication之后,会再去调用successHandler。或者未通过认证,去调用failureHandler。

认证结果如何在多个请求之间共享

再完成了用户认证处理流程之后,我们思考一下是如何在多个请求之间共享这个认证结果的呢?

因为没有做关于这方面的配置,所以可以联想到默认的方式应该是在session中存入了认证结果。

那么是什么时候存放入session中的呢?

我们可以接着认证流程的源码往后看,在通过attemptAuthentication方法后,如果认证成功,会调用successfulAuthentication,该方法中,不仅调用了successHandler,还有一行比较重要的代码

SecurityContextHolder.getContext().setAuthentication(authResult);

SecurityContextHolder是对于ThreadLocal的封装。 ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。 更多的关于ThreadLocal的原理可以看看我以前的文章。

一般来说同一个接口的请求和返回,都会是在一个线程中完成的。我们在SecurityContextHolder中放入了authResult,再其他地方也可以取出来的。

最后就是在SecurityContextPersistenceFilter中取出了authResult,并存入了session

SecurityContextPersistenceFilter也是一个过滤器,它处于整个Security过滤器链的最前方,也就是说开始验证的时候是最先通过该过滤器,验证完成之后是最后通过。

获取认证用户信息

/**
 * 获取当前登录的用户
 * @return 完整的Authentication
 */
@GetMapping("/me1")
public Object currentUser() {
  return SecurityContextHolder.getContext().getAuthentication();
}

@GetMapping("/me2")
public Object currentUser(Authentication authentication) {
  return authentication;
}

/**
 * @param userDetails
 * @return 只包含了userDetails
 */
@GetMapping("/me3")
public Object cuurentUser(@AuthenticationPrincipal UserDetails userDetails) {
  return userDetails;
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • Servlet 过滤器详细介绍

    Servlet 过滤器详细介绍

    这篇文章介绍了Servlet 过滤器,有需要的朋友可以参考一下
    2013-10-10
  • 基于controller使用map接收参数的注意事项

    基于controller使用map接收参数的注意事项

    这篇文章主要介绍了基于controller使用map接收参数的注意事项,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • Java常见的转义字符举例详解

    Java常见的转义字符举例详解

    在java字符常量中,反斜杠(\)是一个特殊的字符,被称为转义字符,它的作用是用来转义后面一个字符,这篇文章主要给大吉介绍了关于Java常见转义字符的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-02-02
  • SpringBoot与spring security的结合的示例

    SpringBoot与spring security的结合的示例

    这篇文章主要介绍了SpringBoot与spring security的结合的示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-03-03
  • Java中方法优先调用可选参数还是固定参数

    Java中方法优先调用可选参数还是固定参数

    这篇文章主要介绍了Java中方法优先调用可选参数还是固定参数,可选参数是 JDK 5 中新增的特性,也叫变长参数或可变参数,固定参数的概念恰好与可选参数相反,固定参数也就是普通的参,下文更多详细内容需要的小伙伴可以参考一下
    2022-05-05
  • JAVA OOM内存溢出问题深入解析

    JAVA OOM内存溢出问题深入解析

    这篇文章主要为大家介绍了JAVA OOM内存溢出问题深入解析,在生产环境抢修中,我们经常会碰到应用系统java内存OOM的情况,这个问题非常常见,今天我们就这个问题来深入学习探讨一下
    2023-10-10
  • 使用SpringBoot项目导入openfeign版本的问题

    使用SpringBoot项目导入openfeign版本的问题

    这篇文章主要介绍了使用SpringBoot项目导入openfeign版本的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • Java实现的AES256加密解密功能示例

    Java实现的AES256加密解密功能示例

    这篇文章主要介绍了Java实现的AES256加密解密功能,结合完整实例形式分析了Java实现AES256加密解密功能的步骤与相关操作技巧,需要的朋友可以参考下
    2017-02-02
  • Java多数据源的三种实现方式小结

    Java多数据源的三种实现方式小结

    多数据源是在一个应用程序中配置和使用多个不同的数据库连接,本文主要介绍了Java多数据源的三种实现方式小结,具有一定的参考价值,感兴趣的可以了解一下
    2025-03-03
  • RocketMQ获取指定消息的实现方法(源码)

    RocketMQ获取指定消息的实现方法(源码)

    这篇文章主要给大家介绍了关于RocketMQ获取指定消息的实现方法,文中通过示例代码介绍的非常详细,对大家学习或者使用RocketMQ具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2020-08-08

最新评论