Spring Security 實戰乾貨:理解AuthenticationManager

碼農小胖哥發表於2020-07-24

1. 前言

我們上一篇介紹了UsernamePasswordAuthenticationFilter的工作流程,留下了一個小小的伏筆,作為一個Servlet Filter應該存在一個doFilter實現方法,而它卻沒有,其實它的父類AbstractAuthenticationProcessingFilter提供了具體的實現。稍後我們會根據這個實現引出今天的主角AuthenticationManager,來繼續介紹使用者的認證過程。

2. AbstractAuthenticationProcessingFilter

我們來看看AbstractAuthenticationProcessingFilter的核心方法doFilter的實現:

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方法,這個我們已經在上一文分析了是通過AuthenticationManagerauthenticate方法進行認證邏輯的處理,接下來我們將重點分析這個介面來幫助我們瞭解Spring Seucirty的認證過程。

3. AuthenticationManager

AuthenticationManager這個介面方法非常奇特,入參和返回值的型別都是Authentication。該介面的作用是對使用者的未授信憑據進行認證,認證通過則返回授信狀態的憑據,否則將丟擲認證異常AuthenticationException

3.1 AuthenticationManager的初始化流程

那麼AbstractAuthenticationProcessingFilter中的 AuthenticationManager 是在哪裡配置的呢? 看過Spring Security 實戰乾貨系列應該知道WebSecurityConfigurerAdapter中的void configure(AuthenticationManagerBuilder auth)是配置AuthenticationManager 的地方, 我根據原始碼總結了一下AuthenticationManager 的初始化流程,相信可以幫助你去閱讀相關的原始碼:

AuthenticationManager的初始化流程

需要注意的是如果我們使用自定義配置一定不能按照類似下面的錯誤示範:

@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的認證流程

從這裡我們可以看出認證管理器AuthenticationManager針對特定的Authentication提供了特定的認證功能,我們可以藉此來實現多種認證並存。

4. 總結

通過本文我們對Spring Security認證管理器AuthenticationManager的初始化過程和認證過程進行了分析,如果你熟悉了AuthenticationManager的邏輯可以實現多種認證方式的並存等能力,實現很多有用的邏輯,這對整合Spring Security到專案中非常重要。多多關注:碼農小胖哥 獲取更多的原創乾貨。

關注公眾號:Felordcn 獲取更多資訊

個人部落格:https://felord.cn

相關文章