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:服务器重启后身份验证保持不变