10.1. `AuthenticationManager`、`ProviderManager` 及 `AuthenticationProvider`

10.1. AuthenticationManagerProviderManagerAuthenticationProvider

AuthenticationManager 是一個介面,而預設的實作就是 ProviderManager,而 ProviderManager 還會將驗證請求代理給一連串設定好的 AuthenticationProvider 去操作,而 AuthenticationProvider 會被依序詢問是否能執行驗證,而每個 provider 要馬丟例外,要馬就回傳裝著使用者 context 資訊的 Authentication 物件。

常見的 provider 驗證方式就是取得使用端對應的資料並裝進 UserDetails,再比對 UserDetails 內的密碼及使用者輸入的密碼,這也是 DaoAuthenticationProvider 採取的方式。

若驗證通過,則用這個裝著使用端資料的 UserDetails,特別是他持有的 GrantedAuthority,去構建 Authentication,並存放在 SecurityContext

如果使用命名空間標籤 <authentication-manager> 做設定,則一個 ProviderManager 的實例會在內部被建立並維護,接著再將 provider 透過 <authentication-provider> 加入。但如果使用這種方式,則不應該在應用程式 context 中宣告 ProviderManager bean。

若不要使用命名空間的方式宣告,則可能會像這樣:

<bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager">
  <constructor-arg>
    <list>
      <ref local="daoAuthenticationProvider"/>
      <ref local="anonymousAuthenticationProvider"/>
      <ref local="ldapAuthenticationProvider"/>
    </list>
  </constructor-arg>
</bean>

上面這個例子表示我們使用三種 provider,並且會依序試圖執行驗證,或跳過驗證只回傳 null。

如果每個 provider 都回傳 null,ProviderManager 就會拋出 ProviderNotFoundException

在驗證機制 authentication mechanism 中,例如 web form-login processing filter,會被注入 ProviderManager 的參考,並且會呼叫它處理驗證請求。

10.1.1. 成功驗證後清除憑證 Credentials

從 3.1 開始,Spring Security 就預設 ProviderManager 會在成功驗證後嘗試清除被返回的 Authentication 內的敏感憑證資訊,以避免像是密碼這種資訊被系統不合理的持有過久。

但這在你用緩存 cache 保留使用者物件的時候可能會造成問題。

例如為了提升無狀態紀錄應用程式的效能,在 Authentication 保留了在緩存裡一個物件的參考,例如是一個 UserDetails 實例,但一旦因為這個機制將憑證移除後,就沒辦法再對緩存裡的資訊作驗證了,所以如果你有使用緩存,可能需要額外考慮到這個問題。

當然,有一個明顯的解決辦法就是預先把物件複製一份,不論是在緩存的實作中,或是會在建立返回的 Authentication 物件的 AuthenticationProvider 中。

還有一個辦法,就是停用 ProviderManagereraseCredentialsAfterAuthentication 屬性,參考 ProviderManager API

10.1.2 DaoAuthenticationProvider

最簡單的 AuthenticationProvider 實作就是 DaoAuthenticationProvider

DaoAuthenticationProviderUserDetailsService 當作 DAO 使用來取得 username、password 及 GrantedAuthority,它驗證的機制只會比對使用端提交給 UsernamePasswordAuthenticationToken 的密碼及 UserDetailsService 取得的密碼。

要設定這個 provider 也很簡單,例如:

<bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
  <property name="userDetailsService" ref="inMemoryDaoImpl"/>
  <property name="passwordEncoder" ref="passwordEncoder"/>
</bean>

PasswordEncoder 是選用的,它會對 UserDetailsService 取得並放到 UserDetails 的密碼做編碼及解碼。