關(guān)于SpringSecurity認(rèn)證邏輯源碼分析
SpringSecurity源碼分析-認(rèn)證邏輯
1. Spring-security-core包中的三個重要類
SecurityContext
- 這個類中就兩個方法getAuthentication()和setAuthentication()
- 這個類用來存儲Authentication對象
public interface SecurityContext extends Serializable { Authentication getAuthentication(); void setAuthentication(Authentication var1); }
Authentication
- 這個類是貫穿SpringSecurity整個流程的一個類。
- 它是一個接口,它的實(shí)現(xiàn)類中的UsernamePasswordAuthenticationToken是通過用戶名密碼認(rèn)證的實(shí)現(xiàn)
- 登錄成功后用來存儲當(dāng)前的登錄信息。
其中三個方法:
- getCredentials():獲取當(dāng)前用戶憑證
- getDetails():獲取當(dāng)前登錄用戶詳情
- getPrincipal():獲取當(dāng)前登錄用戶對象
- isAuthenticated():是否登錄
GrantedAuthority類是用來存儲權(quán)限的,它是一個接口,常用的SimpleGrantedAuthority實(shí)現(xiàn)類,用來存儲用戶包含的權(quán)限
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對象
- 其中有兩個靜態(tài)方法getContext()和setContext()
- 因此,獲得SecurityContextHolder對象就能獲得SecurityContext對象,也就可以獲取Authentication對象,也就可以獲取當(dāng)前的登錄信息。
- initialize():獲取存儲策略,全局、本地線程、父子線程三種,默認(rèn)本地線程。
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); } }
小結(jié):Authentication用來存儲認(rèn)證信息,SecurityContext用來存儲認(rèn)證信息的容器,SecurityContextHolder用來定義容器的存儲策略。
2. 基于用戶名密碼認(rèn)證的流程
找到UsernamePasswordAuthenticationFilter,找到doFilter方法
- 發(fā)現(xiàn)沒有doFilter方法,去父類AbstractAuthenticationProcessingFilter中查看
- 其實(shí)是這樣一個邏輯
- 所有AbstractAuthenticationProcessingFilter的實(shí)現(xiàn)類都調(diào)用父類中的doFilter方法
- 在doFilter方法中調(diào)用了attemptAuthentication方法
- attemptAuthentication方法是一個抽象方法,子類去實(shí)現(xiàn)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); //認(rèn)證操作,ProviderManager中執(zhí)行 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 { //認(rèn)證邏輯 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); } //認(rèn)證成功后的邏輯....... successfulAuthentication(request, response, chain, authResult); } //認(rèn)證邏輯的抽象方法,交給子類去實(shí)現(xiàn) public abstract Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException; }
查看子類UsernamePasswordAuthenticationFilter中的attemptAuthentication方法
- 認(rèn)證的邏輯在這里: this.getAuthenticationManager().authenticate(authRequest)
- 認(rèn)證完畢后封裝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 { // 認(rèn)證邏輯 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,一般從數(shù)據(jù)庫讀取 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 { //調(diào)用我們自己的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); } }
小結(jié):至此返回Authentication對象完成認(rèn)證
3. 認(rèn)證成功后的邏輯
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); } //將認(rèn)證后的對象放到SecurityContext中 SecurityContextHolder.getContext().setAuthentication(authResult); //記住我的執(zhí)行邏輯 rememberMeServices.loginSuccess(request, response, authResult); // 發(fā)布認(rèn)證成功后的時間,可以自定義監(jiān)聽器 if (this.eventPublisher != null) { eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent( authResult, this.getClass())); } //認(rèn)證成功后的執(zhí)行邏輯,默認(rèn)三種,可以通過實(shí)現(xiàn)AuthenticationSuccessHandler接口自定義認(rèn)證成功后邏輯 successHandler.onAuthenticationSuccess(request, response, authResult); }
4. 記住我是如何實(shí)現(xiàn)的
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,傳入的值可以為以下內(nèi)容 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); } }
持久化操作有兩個實(shí)現(xiàn)JdbcTokenRepositoryImpl存到數(shù)據(jù)庫,InMemoryTokenRepositoryImpl存到內(nèi)存中
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 { /** 根據(jù)不同的異常,做出不同的處理*/ 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.登錄頁面是如何產(chǎn)生的
答案在最后一個過濾器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 { //拼接生產(chǎn)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); } }
總結(jié)
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Future與FutureTask接口實(shí)現(xiàn)示例詳解
這篇文章主要為大家介紹了Future與FutureTask接口實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10springboot項(xiàng)目不加端口號也可以訪問項(xiàng)目的方法步驟分析
這篇文章主要介紹了springboot項(xiàng)目不加端口號也可以訪問項(xiàng)目的方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04Spring中的@ExceptionHandler注解詳解與應(yīng)用示例
本文詳細(xì)介紹了Spring框架中的@ExceptionHandler注解的用法,包括基本用法、全局異常處理、結(jié)合@ResponseStatus注解以及返回值類型,通過示例展示了如何使用@ExceptionHandler注解處理不同類型的異常,并提供定制化的異常處理響應(yīng),需要的朋友可以參考下2024-11-11Oracle+Mybatis的foreach insert批量插入報錯的快速解決辦法
本文給大家介紹Oracle+Mybatis的foreach insert批量插入報錯的快速解決辦法,非常不錯,具有參考借鑒價值,感興趣的朋友參考下吧2016-08-08