1. 程式人生 > >Spring Security 實戰乾貨:理解AuthenticationManager

Spring Security 實戰乾貨:理解AuthenticationManager

## 1. 前言 我們[上一篇](https://felord.cn/usernamePasswordAuthenticationFilter.html)介紹了`UsernamePasswordAuthenticationFilter`的工作流程,留下了一個小小的伏筆,作為一個**Servlet Filter**應該存在一個`doFilter`實現方法,而它卻沒有,其實它的父類`AbstractAuthenticationProcessingFilter`提供了具體的實現。稍後我們會根據這個實現引出今天的主角`AuthenticationManager`,來繼續介紹使用者的認證過程。 ## 2. AbstractAuthenticationProcessingFilter 我們來看看`AbstractAuthenticationProcessingFilter`的核心方法`doFilter`的實現: ```java public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; // 先通過請求的uri來判斷是否需要認證,比如預設的/login if (!requiresAuthentication(request, response)) { chain.doFilter(request, response); return; } if (logger.isDebugEnabled()) { logger.debug("Request is to process authentication"); } Authentication authResult; try { // 接著就是執行子類鉤子方法attemptAuthentication來獲取認證結果物件Authentication ,這個物件不能是空 否則直接返回 authResult = attemptAuthentication(request, response); if (authResult == null) { // return immediately as subclass has indicated that it hasn't completed // authentication return; } // 處理session 策略,這裡預設沒有任何策略 sessionStrategy.onAuthentication(authResult, request, response); } catch (InternalAuthenticationServiceException failed) { logger.error( "An internal error occurred while trying to authenticate the user.", failed); // 如果遇到異常 就會交給認證失敗處理器 AuthenticationFailureHandler 來處理 unsuccessfulAuthentication(request, response, failed); return; } catch (AuthenticationException failed) { // Authentication failed unsuccessfulAuthentication(request, response, failed); return; } // 認證成功後繼續其它過濾器鏈 並最終交給認證成功處理器 AuthenticationSuccessHandler 處理 if (continueChainBeforeSuccessfulAuthentication) { chain.doFilter(request, response); } successfulAuthentication(request, response, chain, authResult); } ``` 大部分邏輯這裡是清晰的,關鍵在於`attemptAuthentication`方法,這個我們已經在上一文分析了是通過`AuthenticationManager`的`authenticate`方法進行認證邏輯的處理,接下來我們將重點分析這個介面來幫助我們瞭解**Spring Seucirty**的認證過程。 ## 3. AuthenticationManager `AuthenticationManager`這個介面方法非常奇特,入參和返回值的型別都是`Authentication`。該介面的作用是對使用者的未授信憑據進行認證,認證通過則返回授信狀態的憑據,否則將丟擲認證異常`AuthenticationException`。 ### 3.1 AuthenticationManager的初始化流程 那麼`AbstractAuthenticationProcessingFilter`中的 `AuthenticationManager` 是在哪裡配置的呢? 看過[Spring Security 實戰乾貨系列](https://felord.cn/categories/spring-security/)應該知道`WebSecurityConfigurerAdapter`中的`void configure(AuthenticationManagerBuilder auth)`是配置`AuthenticationManager` 的地方, 我根據原始碼總結了一下`AuthenticationManager` 的初始化流程,相信可以幫助你去閱讀相關的原始碼: ![AuthenticationManager的初始化流程](https://img2020.cnblogs.com/other/1739473/202007/1739473-20200724102137897-2115499421.png) 需要注意的是如果我們使用自定義配置一定不能按照類似下面的錯誤示範: ```java @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); daoAuthenticationProvider.setUserDetailsService(weChatSecurityConfigProperties.getUserDetailsService()); daoAuthenticationProvider.setPasswordEncoder(multiPasswordEncoder()); auth.authenticationProvider(daoAuthenticationProvider); // 呼叫 super 將導致不生效 所以下面語句不要寫 super.configure(auth); } ``` ## 3.2 AuthenticationManager的認證過程 `AuthenticationManager`的實現`ProviderManager`管理了眾多的`AuthenticationProvider`。每一個`AuthenticationProvider`都只支援特定型別的`Authentication`,如果不支援將會跳過。另一個作用就是對適配的`Authentication`進行認證,只要有一個認證成功,那麼就認為認證成功,所有的都沒有通過才認為是認證失敗。認證成功後的`Authentication`就變成授信憑據,並觸發認證成功的事件。認證失敗的就丟擲異常觸發認證失敗的事件。 ![ProviderManager的認證流程](https://img2020.cnblogs.com/other/1739473/202007/1739473-20200724102138325-1742999528.png) 從這裡我們可以看出認證管理器`AuthenticationManager`針對特定的`Authentication`提供了特定的認證功能,我們可以藉此來實現多種認證並存。 ## 4. 總結 通過本文我們對**Spring Security**認證管理器`AuthenticationManager`的初始化過程和認證過程進行了分析,如果你熟悉了`AuthenticationManager`的邏輯可以實現多種認證方式的並存等能力,實現很多有用的邏輯,這對整合**Spring Security**到專案中非常重要。多多關注:**碼農小胖哥** 獲取更多的原創乾貨。 `關注公眾號:Felordcn 獲取更多資訊` [個人部落格:https://felord.cn](https://felord.cn)