Spring Security 认证流程源码详解

Spring Security 过滤链

在这里插入图片描述

认证流程源码详解

UsernamePasswordAuthenticationFilter会拦截用户登录请求,整体流程如下图
在这里插入图片描述
我们来看一下 UsernamePasswordAuthenticationFilter 的源码

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
    public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
    private String usernameParameter = "username";
    private String passwordParameter = "password";
    private boolean postOnly = true;

    public UsernamePasswordAuthenticationFilter() {
        super(new AntPathRequestMatcher("/login", "POST"));
    }

    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();
            
            // 封装成 UsernamePasswordAuthenticationToken 对象
            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);
}

接下来调用

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。新版本里吧DaoAuthenticationProvider 继承了 AbstractUserDetailsAuthenticationProvider 了。

下面接着看 provider.authenticate(authentication)方法

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是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。

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

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

参考文章:https://blog.csdn.net/liyang_com/article/details/83017729

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值