1. 前言
SpringSecurity的認(rèn)證,其實就是我們的登錄驗證。
Web系統(tǒng)中登錄驗證的核心就是**憑證**,比較多使用的是`Session`和`JWT`,其原理都是在用戶成功登錄后返回給用戶一個憑證,后續(xù)用戶訪問時需要攜帶憑證來辨別自己的身份。后端會根據(jù)這個憑證進(jìn)行安全判斷,如果憑證沒問題則代表已登錄,否則則直接拒絕請求。
以下內(nèi)容會先分析源碼理清楚認(rèn)證的流程。
2. SpringSecurity的工作流程
要想分析SpringSecurity的認(rèn)證流程,就一定要先了解整個SpringSecurity的工作流程,我們才能最終進(jìn)行一些自定義操作。
Spring Security的web基礎(chǔ)是Filters(過濾器),它是通過一層層的Filters來對web請求做處理的,每一個web請求會經(jīng)過一條過濾器鏈,在這個過程中完成認(rèn)證與授權(quán)。
其具體工作流程是這樣的:
-
在Spring的過濾器鏈中,Spring Security向其添加了一個
FilterChainProxy過濾器,而這個過濾器只是一個代理過濾器,通過這個代理過濾器創(chuàng)建一套SpringSecurity自定義的過濾器鏈(認(rèn)證與授權(quán)過濾器就在這過濾器鏈中),然后再執(zhí)行這一系列自定義的過濾器。如圖所示(網(wǎng)上找的)authentication_0.jpg -
然后我們可以來看看這個代理過濾器
FilterChainProxy的部分源碼:FilterChainProxy.jpg下面debug程序,設(shè)置斷點,看看過濾器鏈中有哪些過濾器
getFilters.jpg這些過濾器中,我們重點關(guān)注
UsernamePasswordAuthenticationFilter和FilterSecurityInterceptor,其中UsernamePasswordAuthenticationFilter負(fù)責(zé)登錄認(rèn)證,FilterSecurityInterceptor負(fù)責(zé)授權(quán)。 -
SpringSecurity的基本原理(網(wǎng)絡(luò)上找的一張圖)
authentication_1.jpeg如圖所示,一個請求想要訪問到API就會從左到右經(jīng)過藍(lán)線框里的過濾器,其中綠色部分是負(fù)責(zé)認(rèn)證的過濾器,藍(lán)色部分是負(fù)責(zé)異常處理,橙色部分則是負(fù)責(zé)授權(quán),對應(yīng)了我們代碼debug的過濾器鏈。
注意:只有在配置中打開了
formLogin配置項,過濾器鏈中才會加入它們,否則是不會被加到過濾器鏈的。SpringSecurity中有兩個配置項叫
formLogin和httpBasic,分別對應(yīng)著表單認(rèn)證方式(過濾器是UsernamePasswordAuthenticationFilter)和Basic認(rèn)證方式(過濾器是BasicAuthenticationFilter),分別對應(yīng)上圖
3. SpringSecurity中的重要組件
Authentication:認(rèn)證接口,存儲了認(rèn)證信息,代表當(dāng)前登錄用戶SecurityContext:上下文對象,Authentication對象會放在里面SecurityContextHolder:用于拿到上下文對象的靜態(tài)工具類AuthenticationManager:用于校驗Authentication,返回一個認(rèn)證完成后的Authentication對象
我們需要通過
SecurityContext上下文對象來獲取認(rèn)證對象Authentication,而SecurityContext又是交給SecurityContextHolder進(jìn)行管理的。-
查看源碼
-
SecurityContext:
SecurityContext.jpg接口只有兩個方法,作用就是get/set Authentication
-
SecurityContextHolder:
SecurityContextHolder.jpg可以人為這是
SecurityContext的一個靜態(tài)工具類,主要有g(shù)et,set,clear處理SecurityContext,其原理是使用ThreadLocal來保證一個線程中傳遞同一個對象
-
-
我們可以通過以下代碼在程序任何地方獲取
AuthenticationAuthentication authentication = SecurityContextHolder.getContext().getAuthentication(); -
再看看
Authentication和AuthenticationManager源碼Authentication: (注釋太長不好截圖)
public interface Authentication extends Principal, Serializable { Collection<? extends GrantedAuthority> getAuthorities(); Object getCredentials(); Object getDetails(); Object getPrincipal(); boolean isAuthenticated(); void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; }這幾個方法的作用如下:
-
getAuthorities:獲取用戶權(quán)限(角色信息) -
getCredentials:獲取證明用戶認(rèn)證的信息,一般是指密碼等信息 -
getDetails:獲取用戶額外的信息 -
getPrincipal:獲取用戶身份信息,在未認(rèn)證的時候獲取的是用戶名,在已認(rèn)證后獲取的是UserDetails對象 -
isAuthenticated:獲取當(dāng)前Authentication是否已認(rèn)證 -
setAuthenticated:設(shè)置當(dāng)前Authentication是否已認(rèn)證(true/false)
AuthenticationManager:
AuthenticationManager.jpg該接口定義了一個認(rèn)證方法,將一個未被認(rèn)證的
Authentication傳入,返回一個已認(rèn)證的Authentication -
-
總結(jié)下SpringSecurity的認(rèn)證流程
將上面四個組件串聯(lián)起來,可以大致了解到認(rèn)證的流程:
- 一個請求帶著身份信息進(jìn)來
- 經(jīng)過
AuthenticationManager進(jìn)行認(rèn)證 - 然后通過
SecurityContextHolder獲取到SecurityContext - 最后將認(rèn)證后的
Authentication放入SecurityContext,這樣下一個請求進(jìn)來就能知道是否已認(rèn)證過
4. 完整源碼流程
有了以上的一些基礎(chǔ)了解后,我們來順著源碼流程走一邊,理清整個認(rèn)證的流程。
基于formLogin的流程分析,SpringSecurity默認(rèn)也是formLogin。
以下源碼我都將注釋去掉,否則太長了!
-
第一步:請求進(jìn)來,到達(dá)
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 static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login", "POST"); private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY; private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY; private boolean postOnly = true; public UsernamePasswordAuthenticationFilter() { super(DEFAULT_ANT_PATH_REQUEST_MATCHER); } public UsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) { super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (this.postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } String username = obtainUsername(request); username = (username != null) ? username : ""; username = username.trim(); String password = obtainPassword(request); password = (password != null) ? password : ""; UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); // Allow subclasses to set the "details" property setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } @Nullable protected String obtainPassword(HttpServletRequest request) { return request.getParameter(this.passwordParameter); } @Nullable protected String obtainUsername(HttpServletRequest request) { return request.getParameter(this.usernameParameter); } protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) { authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request)); } public void setUsernameParameter(String usernameParameter) { Assert.hasText(usernameParameter, "Username parameter must not be empty or null"); this.usernameParameter = usernameParameter; } public void setPasswordParameter(String passwordParameter) { Assert.hasText(passwordParameter, "Password parameter must not be empty or null"); this.passwordParameter = passwordParameter; } public void setPostOnly(boolean postOnly) { this.postOnly = postOnly; } public final String getUsernameParameter() { return this.usernameParameter; } public final String getPasswordParameter() { return this.passwordParameter; } }分析:
過濾器中定義了一些默認(rèn)的信息,比如默認(rèn)用戶名參數(shù)為username,密碼參數(shù)為password,默認(rèn)請求為
/login,但同時也提供了set、get方法讓我們自定義,自定義的方式就是在配置類WebSecurityConfigurerAdapter的子類中重寫configure(HttpSecurity http)設(shè)置過濾器的處理核心就是
doFilter,但我們在UsernamePasswordAuthenticationFilter中并沒有看到,這是因為在他父類AbstractAuthenticationProcessingFilter實現(xiàn)了。-
進(jìn)入
AbstractAuthenticationProcessingFilter查看@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain); } private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { // 先通過請求的uri判斷是否需要認(rèn)證,比如默認(rèn)的/login就不需要認(rèn)證了 if (!requiresAuthentication(request, response)) { chain.doFilter(request, response); return; } try { Authentication authenticationResult = attemptAuthentication(request, response); if (authenticationResult == null) { // return immediately as subclass has indicated that it hasn't completed return; } this.sessionStrategy.onAuthentication(authenticationResult, request, response); // Authentication success if (this.continueChainBeforeSuccessfulAuthentication) { chain.doFilter(request, response); } successfulAuthentication(request, response, chain, authenticationResult); } catch (InternalAuthenticationServiceException failed) { this.logger.error("An internal error occurred while trying to authenticate the user.", failed); unsuccessfulAuthentication(request, response, failed); } catch (AuthenticationException ex) { // Authentication failed unsuccessfulAuthentication(request, response, ex); } } protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,Authentication authResult) throws IOException, ServletException { SecurityContext context = SecurityContextHolder.createEmptyContext(); context.setAuthentication(authResult); SecurityContextHolder.setContext(context); if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult)); } this.rememberMeServices.loginSuccess(request, response, authResult); if (this.eventPublisher != null) { this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass())); } this.successHandler.onAuthenticationSuccess(request, response, authResult); }分析:
- doFilter首先判斷uri是否需要認(rèn)證
- 接著執(zhí)行方法
Authentication authenticationResult = attemptAuthentication(request, response);進(jìn)行認(rèn)證,從函數(shù)名也能看出是嘗試認(rèn)證,認(rèn)證成功獲取認(rèn)證對象Authentication,這是這個過濾器的核心 - 認(rèn)證成功,則執(zhí)行
successfulAuthentication(),將已認(rèn)證的Authentication存放到SecurityContext,認(rèn)證失敗則通過認(rèn)證失敗處理器AuthenticationFailureHandler處理 - 接下來研究下
attemptAuthentication方法,這個方法在當(dāng)前父類中是一個抽象方法,由子類實現(xiàn),而AbstractAuthenticationProcessingFilter的一個子類就是UsernamepasswordAuthenticationFilter,回到這個類看看這個attemptAuthentication方法
-
分析
UsernamepasswordAuthenticationFilter的attemptAuthentication方法@Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (this.postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } String username = obtainUsername(request); username = (username != null) ? username : ""; username = username.trim(); String password = obtainPassword(request); password = (password != null) ? password : ""; UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); // Allow subclasses to set the "details" property setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); }分析:
從源碼可以看出,首先如果不是POST請求,直接拋出異常
然后從當(dāng)前請求中獲取用戶名username和密碼password
-
通過當(dāng)前用戶名和密碼,構(gòu)造一個令牌
UsernamePasswordAuthenticationToken這個
UsernamePasswordAuthenticationToken繼承了AbstractAuthenticationToken,而AbstractAuthenticationToken又實現(xiàn)了Authentication接口,所以實質(zhì)上這個token就是一個Authentication對象 最后調(diào)用
this.getAuthenticationManager().authenticate(authRequest)返回,這里就用到了AuthenticationManager去認(rèn)證了,這個稍后在看
-
接著看這個方法
this.getAuthenticationManager().authenticate(authRequest)這里使用的是
AuthenticationManager接口的方法去進(jìn)行認(rèn)證,這個方法authenticate很奇特,傳入的參數(shù)和返回值類型都是Authentication.public interface AuthenticationManager { Authentication authenticate(Authentication authentication) throws AuthenticationException; }該接口方法的作用是:**對用戶未認(rèn)證的憑據(jù)進(jìn)行認(rèn)證,認(rèn)證通過后返回已認(rèn)證的憑據(jù),否則拋出認(rèn)證異常
AuthenticationException分析:
從源碼可以看到,這個
AuthenticationManager是一個接口,所以他并不是真正做事情的那個,只是提供了一個標(biāo)準(zhǔn),真正實現(xiàn)功能的是它的子類。通過(ctrl + h)查看
AuthenticationManager接口的實現(xiàn)類,可以看到如下:AuthenticationManager-impl.jpg其他幾個都是內(nèi)部類,所以我們找到了
ProviderManager實現(xiàn)了AuthenticationManager我們看看他實現(xiàn)的
authenticate方法:@Override 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; int currentPosition = 0; int size = this.providers.size(); // 遍歷AuthenticationProvider,列表中的每個Provider依次進(jìn)行認(rèn)證 for (AuthenticationProvider provider : getProviders()) { if (!provider.supports(toTest)) { continue; } if (logger.isTraceEnabled()) { logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)", provider.getClass().getSimpleName(), ++currentPosition, size)); } try { // 真正的驗證 result = provider.authenticate(authentication); if (result != null) { copyDetails(authentication, result); break; } } catch (AccountStatusException | InternalAuthenticationServiceException ex) { prepareException(ex, authentication); // SEC-546: Avoid polling additional providers if auth failure is due to // invalid account status throw ex; } catch (AuthenticationException ex) { lastException = ex; } } // 如果 AuthenticationProvider 列表中的Provider都認(rèn)證失敗,且之前有構(gòu)造一個 AuthenticationManager 實現(xiàn)類,那么利用AuthenticationManager 實現(xiàn)類 繼續(xù)認(rèn)證 if (result == null && this.parent != null) { // Allow the parent to try. try { parentResult = this.parent.authenticate(authentication); result = parentResult; } catch (ProviderNotFoundException ex) { } catch (AuthenticationException ex) { parentException = ex; lastException = ex; } } // 認(rèn)證成功 if (result != null) { if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) { // Authentication is complete. Remove credentials and other secret data // from authentication // 認(rèn)證成功后刪除驗證信息 ((CredentialsContainer) result).eraseCredentials(); } // If the parent AuthenticationManager was attempted and successful then it // will publish an AuthenticationSuccessEvent // This check prevents a duplicate AuthenticationSuccessEvent if the parent // AuthenticationManager already published it // 發(fā)布登錄成功事件 if (parentResult == null) { this.eventPublisher.publishAuthenticationSuccess(result); } return result; } // Parent was null, or didn't authenticate (or throw an exception). if (lastException == null) { lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}")); } // If the parent AuthenticationManager was attempted and failed then it will // publish an AbstractAuthenticationFailureEvent // This check prevents a duplicate AbstractAuthenticationFailureEvent if the // parent AuthenticationManager already published it if (parentException == null) { prepareException(lastException, authentication); } throw lastException; }分析:
從源碼中看出,
ProviderManager并不是自己直接對請求進(jìn)行驗證,而是循環(huán)一個AuthenticationProvider列表,列表中每一個provider依次進(jìn)行判斷是否使用它進(jìn)行驗證。 -
接下來看看
AuthenticationProvider這個
AuthenticationProvider也是一個接口public interface AuthenticationProvider { // 認(rèn)證方法 Authentication authenticate(Authentication authentication) throws AuthenticationException; // 該Provider是否支持對應(yīng)的Authentication類型 boolean supports(Class<?> authentication); }同樣看看這個接口有哪些實現(xiàn)類:
AuthenticationProvider.jpg這個接口的實現(xiàn)類和繼承類有很多,我們直接看與
User相關(guān)的,會看到有一個AbstractUserDetailsAuthenticationProvider抽象類,他的實現(xiàn)類是DaoAuthenticationProvider, 才是真正做驗證的人authenticate
是由AbstractUserDetailsAuthenticationProvider`實現(xiàn)的,源碼如下:@Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,() -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports","Only UsernamePasswordAuthenticationToken is supported")); // 判斷用戶名是否為空 String username = determineUsername(authentication); boolean cacheWasUsed = true; // 先查緩存 UserDetails user = this.userCache.getUserFromCache(username); if (user == null) { cacheWasUsed = false; try { // retrieveUser是一個抽象方法,子類中實現(xiàn) user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); } catch (UsernameNotFoundException ex) { this.logger.debug("Failed to find user '" + username + "'"); if (!this.hideUserNotFoundExceptions) { throw ex; } throw new BadCredentialsException(this.messages .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract"); } // 一些檢查 try { this.preAuthenticationChecks.check(user); additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } catch (AuthenticationException ex) { if (!cacheWasUsed) { throw ex; } // There was a problem, so try again after checking // we're using latest data (i.e. not from the cache) cacheWasUsed = false; // retrieveUser是一個抽象方法,子類中實現(xiàn) user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); this.preAuthenticationChecks.check(user); additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } this.postAuthenticationChecks.check(user); if (!cacheWasUsed) { this.userCache.putUserInCache(user); } Object principalToReturn = user; if (this.forcePrincipalAsString) { principalToReturn = user.getUsername(); } // 創(chuàng)建一個成功的Authentication對象返回 return createSuccessAuthentication(principalToReturn, authentication, user); }在這個
authenticate方法里,真正做驗證的方法是:retrieveUser,該方法是在子類DaoAuthenticationProvider中實現(xiàn)的@Override protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { prepareTimingAttackProtection(); try { // 通過loadUserByUsername獲取用戶信息,返回一個UserDetails 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); } } // 重寫了父類的方法,對密碼進(jìn)行一些加密操作 @Override protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) { boolean upgradeEncoding = this.userDetailsPasswordService != null && this.passwordEncoder.upgradeEncoding(user.getPassword()); if (upgradeEncoding) { String presentedPassword = authentication.getCredentials().toString(); String newPassword = this.passwordEncoder.encode(presentedPassword); user = this.userDetailsPasswordService.updatePassword(user, newPassword); } return super.createSuccessAuthentication(principal, authentication, user); }分析:
這個
retrieveUser方法,就是調(diào)用UserDetailsService的loadUserByUsername方法,這個UserDetailsService就是一個服務(wù)接口,加載UserDetails,一般是從數(shù)據(jù)庫中去查找用戶,封裝為UserDetails對象返回,找不到就報異常。SpringSecurity默認(rèn)實現(xiàn)了一個UserDetails的實現(xiàn)類User,當(dāng)我們使用將用戶信息存儲在內(nèi)存的方式
auth.inMemoryAuthentication()時,會創(chuàng)建一個InMemoryUserDetailsManager,這個類創(chuàng)建了一個UserDetails的實現(xiàn)類User,同時這個類實現(xiàn)了
UserDetailsManager接口,而UserDEtailsManager又是繼承自UserDetailsService,所以默認(rèn)情況下的話就是調(diào)用InMemoryUserDetailsManager類的loadUserByUsername方法因此,當(dāng)我們需要自定義時,則需要自己實現(xiàn)
UserDetailsService接口和UserDetails接口 -
UserDetailsService和UserDetails接口UserDetailsService就是定義了一個加載UserDetails的接口,通常我們會實現(xiàn)這個接口,然后從數(shù)據(jù)庫中查詢相關(guān)用戶信息,再返回。public interface UserDetailsService { UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; }UserDetails也是一個接口,在實際開發(fā)中也會對他進(jìn)行定制化,提供核心用戶信息。SpringSecurity處于安全考慮,UserDetails只是存儲用戶信息,這些信息最后會封裝到
Authentication對象中的。public interface UserDetails extends Serializable { // 返回用戶的權(quán)限集合 Collection<? extends GrantedAuthority> getAuthorities(); /** * Returns the password used to authenticate the user. * @return the password */ String getPassword(); /** * Returns the username used to authenticate the user. Cannot return * <code>null</code>. * @return the username (never <code>null</code>) */ String getUsername(); // 用戶賬戶是否過期 boolean isAccountNonExpired(); // 用戶是否被鎖定 boolean isAccountNonLocked(); // 用戶的密碼是否已過期 boolean isCredentialsNonExpired(); // 用戶是否被禁用 boolean isEnabled(); } -
至此,整個認(rèn)證流程差不多就走完了,這個過程中,梳理以下,我們是以默認(rèn)的登錄方式來分析流程的,默認(rèn)的登錄方式用到的是:
UsernamePasswordAuthenticationFilter和UsernamePasswordAuthenticationToken以及DaoAuthenticationProvider這些來進(jìn)行身份的驗證,那么以后我們要添加別的驗證方式的話,就可以模仿這個流程:重新繼承AbstractAuthenticationProcessingFilter,AbstractAuthenticationToken,AuthenticationProvider。流程圖大致如下:
AuthenticationProvider-1.jpg
-
返回過程
DaoAuthenticationProvider類的retrieveUser方法通過loadUserByUsername獲取到用戶信息后返回一個UserDetails對象給到父類AbstractUserDetailsAuthenticationProvider的方法authenticate中AbstractUserDetailsAuthenticationProvider拿到返回的UserDetails后,調(diào)用了return createSuccessAuthentication(principalToReturn, authentication, user);創(chuàng)建了一個可信的UsernamepasswordAuthenticationToken,并返回給了ProviderManager的authenticate方法這時候的
UsernamepasswordAuthenticationToken是已驗證過的可信的,再往上返回Authentication(UsernamepasswordAuthenticationToken是他的一個實現(xiàn)類,多態(tài))再回到了
UsernamepasswordAuthenticationFilter類的attemptAuthentication方法中,return this.getAuthenticationManager().authenticate(authRequest)返回到了AbstractAuthenticationProcessingFilter類中doFilter,最后調(diào)用了successfulAuthentication,將可信的Authentication對象保存到SecurityContext中,然后放行。-
認(rèn)證成功Handler與失敗Handler
UsernamepasswordAuthenticationFilter的父類AbstractAuthenticationProcessingFilter類的doFilter方法中,認(rèn)證成功會調(diào)用successfulAuthentication,失敗調(diào)用unsuccessfulAuthentication。在
AbstractAuthenticationProcessingFilter中對successfulAuthentication方法和unsuccessfulAuthentication做了默認(rèn)實現(xiàn),源碼如下:protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { SecurityContext context = SecurityContextHolder.createEmptyContext(); context.setAuthentication(authResult); SecurityContextHolder.setContext(context); if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult)); } this.rememberMeServices.loginSuccess(request, response, authResult); if (this.eventPublisher != null) { this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass())); } this.successHandler.onAuthenticationSuccess(request, response, authResult); } protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { SecurityContextHolder.clearContext(); this.logger.trace("Failed to process authentication request", failed); this.logger.trace("Cleared SecurityContextHolder"); this.logger.trace("Handling authentication failure"); this.rememberMeServices.loginFail(request, response); this.failureHandler.onAuthenticationFailure(request, response, failed); }分析:
- 方法最后分別對認(rèn)證成功和失敗做了自定義處理,最后分別調(diào)用了兩個Handler處理,
AuthenticationSuccessHandler類型的successHandler和AuthenticationFailureHandler類型的failureHandler。 - 在
AbstractAuthenticationProcessingFilter類中還分別提供了setAuthenticationSuccessHandler方法和setAuthenticationFailureHandler能讓我們實現(xiàn)自定義Handler的注入。 - 綜上分析,我們要自定義Handler就有兩種方式了
- 自定義
AuthenticationSuccessHandler類和AuthenticationFailureHandler分別實現(xiàn)onAuthenticationSuccess方法和onAuthenticationFailure方法,然后調(diào)用提供的set方法注入到類中 -
AbstractAuthenticationProcessingFilter的子類中去重寫successfulAuthentication方法和unsuccessfulAuthentication方法。
- 自定義
- 方法最后分別對認(rèn)證成功和失敗做了自定義處理,最后分別調(diào)用了兩個Handler處理,
5. 整體流程圖

6. 學(xué)習(xí)博客
【項目實踐】一文帶你搞定前后端分離下的認(rèn)證和授權(quán)|Spring Security + JWT









