SpringSecurity 5.0 认证记住我授权源码分析

Posted 吴wuwu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringSecurity 5.0 认证记住我授权源码分析相关的知识,希望对你有一定的参考价值。

一、SpringSecurity 过滤器链:

  1、SecurityContextPersistenceFilter 会在请求开始时从配置好的SecurityContextRepository中获取SecurityContext,然后把它设置给SecurityContextHolder。
  在请求完成后将SecurityContextHolder持有的SecurityContext再保存到配置好的SecurityContextRepository,同时清除SecurityContextHolder所持有的SecurityContext。
 
  2、UsernamePasswordAuthenticationFilter用于处理来自表单提交的认证,认证成功由AuthenticationSuccessHandler处理,反之由AuthenticationFailureHandler处理。
 
  3、FilterSecurityInterceptor用于保护Http资源(授权),它需要引用AccessDecisionManager(决策管理器)、AuthenticationManager(认证管理器)、                                            SecurityMetadataSource(资源与权限的对应关系)。
 
  4、ExceptionTranslationFilter 只会处理 AuthenticationException、AccessDeniedException异常,其他的异常会抛出。
 

二、基于用户名、密码认证、授权流程:

1、AbstractAuthenticationProcessingFilter.attemptAuthentication()开始用户认证流程,在这里会处理认证的成功(AuthenticationSuccessHandler)或失败(AuthenticationFailureHandler);
 
2、重写UsernamePasswordAuthenticationFilter.attemptAuthentication()调用retrieveUser()方法,自定义用户名、密码、验证码校验、认证逻辑,返回Authentication对象实例存入SessionAuthenticationStrategy;
 
3、继承AbstractUserDetailsAuthenticationProvider重写retrieveUser方法,开始处理用户认证,调用DetailsServiceImpl.loadUserByUsername()返回UserDetails对象实例;
 
4、实现UserDetailsService重写loadUserByUsername,自定义用户名密码校验流程,成功返回UserDetails对象实例,否则抛出AuthenticationException类异常;用户认证结束;
 
5、FilterSecurityInterceptor.beforeInvocation()过滤器开始授权流程,实例化AccessDecisionManager(授权管理器)、AuthenticationManager(认证管理器)、FilterInvocationSecurityMetadataSource(处理url、权限关系的接口);
 
6、进入AbstractSecurityInterceptor.beforeInvocation(Object object)进行授权,分为三步开始处理。
 6.1.Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);//获取当前url的权限
 6.2.Authentication authenticated = authenticateIfRequired();//获取Authenticatio实例,拿到当前用户信息
 6.3.this.accessDecisionManager.decide(authenticated, object, attributes);//没有权限抛出AccessDeniedException类异常 ExceptionTranslationFilter处理异常
 
三、“记住我”功能实现原理:
  1、用户第一次登陆,AbstractAuthenticationProcessingFilter  开始用户认证,成功后 RememberMeServices 实现类 AbstractRememberMeServices作业务处理。
  PersistentTokenBasedRememberMeServices 继承了 AbstractRememberMeServices,重写了 onLoginSucce(),存储 PersistentTokenRepository 对象到DB。
public class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices {
  private PersistentTokenRepository tokenRepository = new InMemoryTokenRepositoryImpl();
    ......
  protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
        String username = successfulAuthentication.getName();
        this.logger.debug("Creating new persistent login for user " + username);
        PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, this.generateSeriesData(), this.generateTokenData(), new Date());

        try {
            this.tokenRepository.createNewToken(persistentToken);
            this.addCookie(persistentToken, request, response);
        } catch (Exception var7) {
            this.logger.error("Failed to save persistent token ", var7);
        }

    }
   ......
}

  2、第二次登陆 RememberMeAuthenticationFilter 处理

public class RememberMeAuthenticationFilter extends GenericFilterBean implements ApplicationEventPublisherAware {
   
......
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request, response);// 解码cookie获取token,拿到UserDetails对象
            if (rememberMeAuth != null) {
                try {
                    rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth);//认证开始
                    SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
                    this.onSuccessfulAuthentication(request, response, rememberMeAuth);
                    if (this.logger.isDebugEnabled()) {
                        this.logger.debug("SecurityContextHolder populated with remember-me token: \'" + SecurityContextHolder.getContext().getAuthentication() + "\'");
                    }

                    if (this.eventPublisher != null) {
                        this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(SecurityContextHolder.getContext().getAuthentication(), this.getClass()));
                    }

                    if (this.successHandler != null) {
                        this.successHandler.onAuthenticationSuccess(request, response, rememberMeAuth);
                        return;
                    }
                } catch (AuthenticationException var8) {
                    if (this.logger.isDebugEnabled()) {
                        this.logger.debug("SecurityContextHolder not populated with remember-me token, as AuthenticationManager rejected Authentication returned by RememberMeServices: \'" + rememberMeAuth + "\'; invalidating remember-me token", var8);
                    }

                    this.rememberMeServices.loginFail(request, response);//失败跳转到登录页
                    this.onUnsuccessfulAuthentication(request, response, var8);
                }
            }

            chain.doFilter(request, response);
        } else {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: \'" + SecurityContextHolder.getContext().getAuthentication() + "\'");
            }

            chain.doFilter(request, response);
        }

    }
}

 

以上是关于SpringSecurity 5.0 认证记住我授权源码分析的主要内容,如果未能解决你的问题,请参考以下文章

Spring Security:服务器重启后身份验证保持不变

springsecurity的remember me

Spring Security 认证管理漏洞

场景应用:SpringSecurity记住我功能实现

SpringBoot集成SpringSecurity(十记住我)

SpringSecurity认证流程分析