关于SpringSecurity认证逻辑源码分析
作者:让你三行代码QAQ
SpringSecurity源码分析-认证逻辑
1. Spring-security-core包中的三个重要类
SecurityContext
- 这个类中就两个方法getAuthentication()和setAuthentication()
- 这个类用来存储Authentication对象
public interface SecurityContext extends Serializable { Authentication getAuthentication(); void setAuthentication(Authentication var1); }
Authentication
- 这个类是贯穿SpringSecurity整个流程的一个类。
- 它是一个接口,它的实现类中的UsernamePasswordAuthenticationToken是通过用户名密码认证的实现
- 登录成功后用来存储当前的登录信息。
其中三个方法:
- getCredentials():获取当前用户凭证
- getDetails():获取当前登录用户详情
- getPrincipal():获取当前登录用户对象
- isAuthenticated():是否登录
GrantedAuthority类是用来存储权限的,它是一个接口,常用的SimpleGrantedAuthority实现类,用来存储用户包含的权限
public interface Authentication extends Principal, Serializable { Collection<? extends GrantedAuthority> getAuthorities(); Object getCredentials(); Object getDetails(); Object getPrincipal(); boolean isAuthenticated(); void setAuthenticated(boolean var1) throws IllegalArgumentException; }
SecurityContextHolder
- 这个对象用来存储SecurityContext对象
- 其中有两个静态方法getContext()和setContext()
- 因此,获得SecurityContextHolder对象就能获得SecurityContext对象,也就可以获取Authentication对象,也就可以获取当前的登录信息。
- initialize():获取存储策略,全局、本地线程、父子线程三种,默认本地线程。
public class SecurityContextHolder { public static SecurityContext getContext() { return strategy.getContext(); } private static void initialize() { if (!StringUtils.hasText(strategyName)) { strategyName = "MODE_THREADLOCAL"; } if (strategyName.equals("MODE_THREADLOCAL")) { strategy = new ThreadLocalSecurityContextHolderStrategy(); } else if (strategyName.equals("MODE_INHERITABLETHREADLOCAL")) { strategy = new InheritableThreadLocalSecurityContextHolderStrategy(); } else if (strategyName.equals("MODE_GLOBAL")) { strategy = new GlobalSecurityContextHolderStrategy(); } else { try { Class<?> clazz = Class.forName(strategyName); Constructor<?> customStrategy = clazz.getConstructor(); strategy = (SecurityContextHolderStrategy)customStrategy.newInstance(); } catch (Exception var2) { ReflectionUtils.handleReflectionException(var2); } } ++initializeCount; } public static void setContext(SecurityContext context) { strategy.setContext(context); } }
小结:Authentication用来存储认证信息,SecurityContext用来存储认证信息的容器,SecurityContextHolder用来定义容器的存储策略。
2. 基于用户名密码认证的流程
找到UsernamePasswordAuthenticationFilter,找到doFilter方法
- 发现没有doFilter方法,去父类AbstractAuthenticationProcessingFilter中查看
- 其实是这样一个逻辑
- 所有AbstractAuthenticationProcessingFilter的实现类都调用父类中的doFilter方法
- 在doFilter方法中调用了attemptAuthentication方法
- attemptAuthentication方法是一个抽象方法,子类去实现AbstractAuthenticationProcessingFilter抽象方法
UsernamePasswordAuthenticationFilter类
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter { //…… …… public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException( "Authentication method not supported: " + request.getMethod()); } String username = obtainUsername(request); String password = obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); //封装成Authentication对象 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( username, password); setDetails(request, authRequest); //认证操作,ProviderManager中执行 return this.getAuthenticationManager().authenticate(authRequest); } //…… …… }
AbstractAuthenticationProcessingFilter类
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; if (!requiresAuthentication(request, response)) { chain.doFilter(request, response); return; } if (logger.isDebugEnabled()) { logger.debug("Request is to process authentication"); } Authentication authResult; try { //认证逻辑 authResult = attemptAuthentication(request, response); if (authResult == null) { // return immediately as subclass has indicated that it hasn't completed // authentication return; } sessionStrategy.onAuthentication(authResult, request, response); } catch (InternalAuthenticationServiceException failed) { logger.error( "An internal error occurred while trying to authenticate the user.", failed); unsuccessfulAuthentication(request, response, failed); return; } catch (AuthenticationException failed) { // Authentication failed unsuccessfulAuthentication(request, response, failed); return; } // Authentication success if (continueChainBeforeSuccessfulAuthentication) { chain.doFilter(request, response); } //认证成功后的逻辑....... successfulAuthentication(request, response, chain, authResult); } //认证逻辑的抽象方法,交给子类去实现 public abstract Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException; }
查看子类UsernamePasswordAuthenticationFilter中的attemptAuthentication方法
- 认证的逻辑在这里: this.getAuthenticationManager().authenticate(authRequest)
- 认证完毕后封装Authentication对象,返回。
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException( "Authentication method not supported: " + request.getMethod()); } String username = obtainUsername(request); String password = obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( username, password); // Allow subclasses to set the "details" property setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); }
ProviderManager类中的authenticate方法
public Authentication authenticate(Authentication authentication) throws AuthenticationException { Class<? extends Authentication> toTest = authentication.getClass(); AuthenticationException lastException = null; AuthenticationException parentException = null; Authentication result = null; Authentication parentResult = null; boolean debug = logger.isDebugEnabled(); Iterator var8 = this.getProviders().iterator(); while(var8.hasNext()) { AuthenticationProvider provider = (AuthenticationProvider)var8.next(); if (provider.supports(toTest)) { if (debug) { logger.debug("Authentication attempt using " + provider.getClass().getName()); } try { // 认证逻辑 result = provider.authenticate(authentication); if (result != null) { this.copyDetails(authentication, result); break; } } catch (AccountStatusException var13) { this.prepareException(var13, authentication); throw var13; } catch (InternalAuthenticationServiceException var14) { this.prepareException(var14, authentication); throw var14; } catch (AuthenticationException var15) { lastException = var15; } } } if (result == null && this.parent != null) { try { result = parentResult = this.parent.authenticate(authentication); } catch (ProviderNotFoundException var11) { } catch (AuthenticationException var12) { parentException = var12; lastException = var12; } } if (result != null) { if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) { ((CredentialsContainer)result).eraseCredentials(); } if (parentResult == null) { this.eventPublisher.publishAuthenticationSuccess(result); } return result; } else { if (lastException == null) { lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}")); } if (parentException == null) { this.prepareException((AuthenticationException)lastException, authentication); } throw lastException; } }
AbstractUserDetailsAuthenticationProvider中的Authentication方法
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; //缓存获取user UserDetails user=this.userCache.getUserFromCache(username); if(user==null){ cacheWasUsed=false; try{ //自定义获取user,一般从数据库读取 user=retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication); } catch(UsernameNotFoundException notFound){ logger.debug("User '"+username+"' not found"); if(hideUserNotFoundExceptions){ throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } else{ throw notFound; } } Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract"); } try{ preAuthenticationChecks.check(user); //去比对密码 additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication); } catch(AuthenticationException exception){ if(cacheWasUsed){ // There was a problem, so try again after checking // we're using latest data (i.e. not from the cache) cacheWasUsed=false; user=retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication); preAuthenticationChecks.check(user); additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication); } else{ throw exception; } } postAuthenticationChecks.check(user); if(!cacheWasUsed){ this.userCache.putUserInCache(user); } Object principalToReturn=user; if(forcePrincipalAsString){ principalToReturn=user.getUsername(); } //封装Authentication对象 return createSuccessAuthentication(principalToReturn,authentication,user); }
DaoAuthenticationProvider中的retrieveUser方法
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { prepareTimingAttackProtection(); try { //调用我们自己的loadUserByUsername方法获取user 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) { mitigateAgainstTimingAttack(authentication); throw ex; } catch (InternalAuthenticationServiceException ex) { throw ex; } catch (Exception ex) { throw new InternalAuthenticationServiceException(ex.getMessage(), ex); } }
小结:至此返回Authentication对象完成认证
3. 认证成功后的逻辑
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { if (logger.isDebugEnabled()) { logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult); } //将认证后的对象放到SecurityContext中 SecurityContextHolder.getContext().setAuthentication(authResult); //记住我的执行逻辑 rememberMeServices.loginSuccess(request, response, authResult); // 发布认证成功后的时间,可以自定义监听器 if (this.eventPublisher != null) { eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent( authResult, this.getClass())); } //认证成功后的执行逻辑,默认三种,可以通过实现AuthenticationSuccessHandler接口自定义认证成功后逻辑 successHandler.onAuthenticationSuccess(request, response, authResult); }
4. 记住我是如何实现的
AbstractRememberMeServices的loginSuccess方法和rememberMeRequested方法
public final void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) { //判断是否勾选remember if (!this.rememberMeRequested(request, this.parameter)) { this.logger.debug("Remember-me login not requested."); } else { this.onLoginSuccess(request, response, successfulAuthentication); } } protected boolean rememberMeRequested(HttpServletRequest request, String parameter) { if (this.alwaysRemember) { return true; } else { String paramValue = request.getParameter(parameter); //判断是否勾选remember,传入的值可以为以下内容 if (paramValue != null && (paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on") || paramValue.equalsIgnoreCase("yes") || paramValue.equals("1"))) { return true; } else { if (this.logger.isDebugEnabled()) { this.logger.debug("Did not send remember-me cookie (principal did not set parameter '" + parameter + "')"); } return false; } } }
PersistentTokenBasedRememberMeServices中的onLoginSuccess方法完成持久化操作
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); //将token放到cookie中,可以自定义 this.addCookie(persistentToken, request, response); } catch (Exception var7) { this.logger.error("Failed to save persistent token ", var7); } }
持久化操作有两个实现JdbcTokenRepositoryImpl存到数据库,InMemoryTokenRepositoryImpl存到内存中
5.Security中的ExceptionTranslationFilter过滤器
- 这个过滤器不处理逻辑
- 只捕获Security中的异常
- 捕获异常后处理异常信息
public class ExceptionTranslationFilter extends GenericFilterBean { // ~ Instance fields // ================================================================================================ private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl(); private AuthenticationEntryPoint authenticationEntryPoint; private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl(); private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer(); private RequestCache requestCache = new HttpSessionRequestCache(); private final MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); public ExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint) { this(authenticationEntryPoint, new HttpSessionRequestCache()); } public ExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint, RequestCache requestCache) { Assert.notNull(authenticationEntryPoint, "authenticationEntryPoint cannot be null"); Assert.notNull(requestCache, "requestCache cannot be null"); this.authenticationEntryPoint = authenticationEntryPoint; this.requestCache = requestCache; } // ~ Methods // ======================================================================================================== @Override public void afterPropertiesSet() { Assert.notNull(authenticationEntryPoint, "authenticationEntryPoint must be specified"); } public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; try { /** 交给下一个过滤器处理*/ chain.doFilter(request, response); logger.debug("Chain processed normally"); } catch (IOException ex) { throw ex; } catch (Exception ex) { /** 捕获Security中的异常,处理异常*/ Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex); RuntimeException ase = (AuthenticationException) throwableAnalyzer .getFirstThrowableOfType(AuthenticationException.class, causeChain); if (ase == null) { ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType( AccessDeniedException.class, causeChain); } if (ase != null) { if (response.isCommitted()) { throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex); } /** 处理异常*/ handleSpringSecurityException(request, response, chain, ase); } else { // Rethrow ServletExceptions and RuntimeExceptions as-is if (ex instanceof ServletException) { throw (ServletException) ex; } else if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } // Wrap other Exceptions. This shouldn't actually happen // as we've already covered all the possibilities for doFilter throw new RuntimeException(ex); } } } public AuthenticationEntryPoint getAuthenticationEntryPoint() { return authenticationEntryPoint; } protected AuthenticationTrustResolver getAuthenticationTrustResolver() { return authenticationTrustResolver; } private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException { /** 根据不同的异常,做出不同的处理*/ if (exception instanceof AuthenticationException) { logger.debug( "Authentication exception occurred; redirecting to authentication entry point", exception); sendStartAuthentication(request, response, chain, (AuthenticationException) exception); } else if (exception instanceof AccessDeniedException) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) { logger.debug( "Access is denied (user is " + (authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point", exception); sendStartAuthentication( request, response, chain, new InsufficientAuthenticationException( messages.getMessage( "ExceptionTranslationFilter.insufficientAuthentication", "Full authentication is required to access this resource"))); } else { logger.debug( "Access is denied (user is not anonymous); delegating to AccessDeniedHandler", exception); accessDeniedHandler.handle(request, response, (AccessDeniedException) exception); } } } protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AuthenticationException reason) throws ServletException, IOException { // SEC-112: Clear the SecurityContextHolder's Authentication, as the // existing Authentication is no longer considered valid SecurityContextHolder.getContext().setAuthentication(null); requestCache.saveRequest(request, response); logger.debug("Calling Authentication entry point."); authenticationEntryPoint.commence(request, response, reason); } public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) { Assert.notNull(accessDeniedHandler, "AccessDeniedHandler required"); this.accessDeniedHandler = accessDeniedHandler; } public void setAuthenticationTrustResolver( AuthenticationTrustResolver authenticationTrustResolver) { Assert.notNull(authenticationTrustResolver, "authenticationTrustResolver must not be null"); this.authenticationTrustResolver = authenticationTrustResolver; } public void setThrowableAnalyzer(ThrowableAnalyzer throwableAnalyzer) { Assert.notNull(throwableAnalyzer, "throwableAnalyzer must not be null"); this.throwableAnalyzer = throwableAnalyzer; } /** * Default implementation of <code>ThrowableAnalyzer</code> which is capable of also * unwrapping <code>ServletException</code>s. */ private static final class DefaultThrowableAnalyzer extends ThrowableAnalyzer { /** * @see org.springframework.security.web.util.ThrowableAnalyzer#initExtractorMap() */ protected void initExtractorMap() { super.initExtractorMap(); registerExtractor(ServletException.class, new ThrowableCauseExtractor() { public Throwable extractCause(Throwable throwable) { ThrowableAnalyzer.verifyThrowableHierarchy(throwable, ServletException.class); return ((ServletException) throwable).getRootCause(); } }); } } }
6.登录页面是如何产生的
答案在最后一个过滤器DefaultLoginPageGeneratingFilter中
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest)req; HttpServletResponse response = (HttpServletResponse)res; boolean loginError = this.isErrorPage(request); boolean logoutSuccess = this.isLogoutSuccess(request); if (!this.isLoginUrlRequest(request) && !loginError && !logoutSuccess) { chain.doFilter(request, response); } else { //拼接生产html登录页面 String loginPageHtml = this.generateLoginPageHtml(request, loginError, logoutSuccess); response.setContentType("text/html;charset=UTF-8"); response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length); response.getWriter().write(loginPageHtml); } }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。