Servlet 驗證架構
本討論擴展了Servlet 安全性:概觀,以描述 Spring Security 的主要架構組件,這些組件用於 Servlet 驗證。如果您需要具體的流程來說明這些組件如何組合在一起,請查看驗證機制特定章節。
-
SecurityContextHolder -
SecurityContextHolder
是 Spring Security 儲存已驗證人員詳細資訊的地方。 -
SecurityContext - 從
SecurityContextHolder
取得,並包含目前已驗證使用者的Authentication
。 -
Authentication - 可以是
AuthenticationManager
的輸入,以提供使用者已提供的憑證以進行驗證,或是來自SecurityContext
的目前使用者。 -
GrantedAuthority - 授與
Authentication
上主體的授權 (即角色、範圍等) -
AuthenticationManager - 定義 Spring Security 的 Filters 如何執行驗證的 API。
-
ProviderManager -
AuthenticationManager
最常見的實作。 -
AuthenticationProvider - 由
ProviderManager
用於執行特定類型的驗證。 -
使用
AuthenticationEntryPoint
請求憑證 - 用於從用戶端請求憑證 (即重新導向至登入頁面、傳送WWW-Authenticate
回應等) -
AbstractAuthenticationProcessingFilter - 用於驗證的基本
Filter
。這也很好地說明了驗證的高階流程以及各個部分如何協同工作。
SecurityContextHolder
Spring Security 驗證模型的核心是 SecurityContextHolder
。它包含 SecurityContext。

SecurityContextHolder
是 Spring Security 儲存已驗證人員詳細資訊的地方。Spring Security 不關心 SecurityContextHolder
如何填入。如果它包含一個值,則會將其用作目前已驗證的使用者。
指示使用者已驗證的最簡單方法是直接設定 SecurityContextHolder
SecurityContextHolder
-
Java
-
Kotlin
SecurityContext context = SecurityContextHolder.createEmptyContext(); (1)
Authentication authentication =
new TestingAuthenticationToken("username", "password", "ROLE_USER"); (2)
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context); (3)
val context: SecurityContext = SecurityContextHolder.createEmptyContext() (1)
val authentication: Authentication = TestingAuthenticationToken("username", "password", "ROLE_USER") (2)
context.authentication = authentication
SecurityContextHolder.setContext(context) (3)
1 | 我們從建立空的 SecurityContext 開始。您應該建立新的 SecurityContext 執行個體,而不是使用 SecurityContextHolder.getContext().setAuthentication(authentication) ,以避免跨多個執行緒的競爭狀況。 |
2 | 接下來,我們建立新的 Authentication 物件。Spring Security 不關心 SecurityContext 上設定的 Authentication 實作類型。在這裡,我們使用 TestingAuthenticationToken ,因為它非常簡單。更常見的生產案例是 UsernamePasswordAuthenticationToken(userDetails, password, authorities) 。 |
3 | 最後,我們在 SecurityContextHolder 上設定 SecurityContext 。Spring Security 使用此資訊進行授權。 |
若要取得有關已驗證主體的資訊,請存取 SecurityContextHolder
。
-
Java
-
Kotlin
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
val context = SecurityContextHolder.getContext()
val authentication = context.authentication
val username = authentication.name
val principal = authentication.principal
val authorities = authentication.authorities
預設情況下,SecurityContextHolder
使用 ThreadLocal
來儲存這些詳細資訊,這表示即使 SecurityContext
未明確作為引數傳遞給這些方法,SecurityContext
也始終可用於相同執行緒中的方法。如果您注意在處理目前主體的請求後清除執行緒,則以這種方式使用 ThreadLocal
是非常安全的。Spring Security 的 FilterChainProxy 可確保始終清除 SecurityContext
。
某些應用程式不太適合使用 ThreadLocal
,因為它們使用執行緒的特定方式。例如,Swing 用戶端可能希望 Java 虛擬機器中的所有執行緒都使用相同的安全性內容。您可以在啟動時使用策略組態 SecurityContextHolder
,以指定您希望如何儲存內容。對於獨立應用程式,您可以使用 SecurityContextHolder.MODE_GLOBAL
策略。其他應用程式可能希望安全執行緒衍生的執行緒也採用相同的安全性身分。您可以使用 SecurityContextHolder.MODE_INHERITABLETHREADLOCAL
來達成此目的。您可以透過兩種方式從預設 SecurityContextHolder.MODE_THREADLOCAL
變更模式。第一種是設定系統屬性。第二種是在 SecurityContextHolder
上呼叫靜態方法。大多數應用程式不需要從預設值變更。但是,如果您這樣做,請查看 SecurityContextHolder
的 JavaDoc 以瞭解更多資訊。
SecurityContext
SecurityContext
是從 SecurityContextHolder 取得的。SecurityContext
包含 Authentication 物件。
Authentication
Authentication
介面在 Spring Security 中有兩個主要用途
-
AuthenticationManager
的輸入,以提供使用者已提供的憑證以進行驗證。在此情境中使用時,isAuthenticated()
傳回false
。 -
表示目前已驗證的使用者。您可以從 SecurityContext 取得目前的
Authentication
。
Authentication
包含
-
principal
:識別使用者。當使用使用者名稱/密碼進行驗證時,這通常是UserDetails
的執行個體。 -
credentials
:通常是密碼。在許多情況下,這會在使用者通過驗證後清除,以確保不會洩漏。 -
authorities
:GrantedAuthority
執行個體是授與使用者的進階權限。兩個範例是角色和範圍。
GrantedAuthority
GrantedAuthority
執行個體是授與使用者的進階權限。兩個範例是角色和範圍。
您可以從 Authentication.getAuthorities()
方法取得 GrantedAuthority
執行個體。此方法提供 GrantedAuthority
物件的 Collection
。GrantedAuthority
顧名思義,是授與主體的授權。此類授權通常是「角色」,例如 ROLE_ADMINISTRATOR
或 ROLE_HR_SUPERVISOR
。稍後會針對 Web 授權、方法授權和領域物件授權組態這些角色。Spring Security 的其他部分會解譯這些授權,並期望它們存在。當使用基於使用者名稱/密碼的驗證時,GrantedAuthority
執行個體通常由 UserDetailsService
載入。
通常,GrantedAuthority
物件是應用程式範圍的權限。它們不是特定於給定領域物件的。因此,您不太可能具有代表 Employee
物件編號 54 權限的 GrantedAuthority
,因為如果存在數千個此類授權,您將很快耗盡記憶體 (或至少導致應用程式花費很長時間來驗證使用者)。當然,Spring Security 經過明確設計以處理此常見需求,但您應該改為將專案的領域物件安全性功能用於此目的。
AuthenticationManager
AuthenticationManager
是定義 Spring Security 的 Filters 如何執行驗證的 API。Authentication
會由呼叫 AuthenticationManager
的控制器 (即 Spring Security 的 Filters
執行個體) 傳回,然後設定在 SecurityContextHolder 上。如果您未與 Spring Security 的 Filters
執行個體整合,您可以直接設定 SecurityContextHolder
,而無需使用 AuthenticationManager
。
雖然 AuthenticationManager
的實作可以是任何東西,但最常見的實作是 ProviderManager
。
ProviderManager
ProviderManager
是 AuthenticationManager
最常用的實作。ProviderManager
委派給 AuthenticationProvider
執行個體的 List
。AuthenticationProvider
執行個體都有機會指示驗證應成功、失敗,或指示它無法做出決定,並允許下游 AuthenticationProvider
做出決定。如果沒有任何已組態的 AuthenticationProvider
執行個體可以驗證,則驗證會失敗,並出現 ProviderNotFoundException
,這是一個特殊的 AuthenticationException
,指示 ProviderManager
未組態為支援傳遞給它的 Authentication
類型。

實際上,每個 AuthenticationProvider
都知道如何執行特定類型的驗證。例如,一個 AuthenticationProvider
可能能夠驗證使用者名稱/密碼,而另一個可能能夠驗證 SAML 斷言。這讓每個 AuthenticationProvider
都能執行非常特定類型的驗證,同時支援多種類型的驗證,並且僅公開單一 AuthenticationManager
Bean。
ProviderManager
也允許組態選用的父系 AuthenticationManager
,如果在沒有 AuthenticationProvider
可以執行驗證的情況下,將會諮詢父系 AuthenticationManager
。父系可以是任何類型的 AuthenticationManager
,但通常是 ProviderManager
的執行個體。

事實上,多個 ProviderManager
執行個體可能會共用相同的父系 AuthenticationManager
。這在有多個 SecurityFilterChain
執行個體的情況下有點常見,這些執行個體有一些共同的驗證 (共用的父系 AuthenticationManager
),但也具有不同的驗證機制 (不同的 ProviderManager
執行個體)。

預設情況下,ProviderManager
會嘗試從成功的驗證請求傳回的 Authentication
物件中清除任何敏感的憑證資訊。這可防止密碼等資訊在 HttpSession
中保留不必要的長時間。
當您使用使用者物件的快取 (例如,為了改善無狀態應用程式中的效能) 時,這可能會導致問題。如果 Authentication
包含對快取中物件 (例如 UserDetails
執行個體) 的參考,且此物件的憑證已移除,則不再可能針對快取值進行驗證。如果您使用快取,則需要考慮到這一點。一個顯而易見的解決方案是先建立物件的複本,無論是在快取實作中,還是在建立傳回的 Authentication
物件的 AuthenticationProvider
中。或者,您可以停用 ProviderManager
上的 eraseCredentialsAfterAuthentication
屬性。請參閱 ProviderManager 類別的 Javadoc。
AuthenticationProvider
您可以將多個 AuthenticationProvider
s 執行個體注入到 ProviderManager
中。每個 AuthenticationProvider
都會執行特定類型的驗證。例如,DaoAuthenticationProvider
支援基於使用者名稱/密碼的驗證,而 JwtAuthenticationProvider
支援驗證 JWT 權杖。
使用 AuthenticationEntryPoint
請求憑證
AuthenticationEntryPoint
用於傳送 HTTP 回應,以從用戶端請求憑證。
有時,用戶端會主動包含憑證 (例如使用者名稱和密碼),以請求資源。在這些情況下,Spring Security 不需要提供從用戶端請求憑證的 HTTP 回應,因為憑證已包含在內。
在其他情況下,用戶端會對他們未經授權存取的資源發出未驗證的請求。在這種情況下,會使用 AuthenticationEntryPoint
的實作來從用戶端請求憑證。AuthenticationEntryPoint
實作可能會執行重新導向至登入頁面、使用 WWW-Authenticate 標頭回應,或採取其他動作。
AbstractAuthenticationProcessingFilter
AbstractAuthenticationProcessingFilter
用作驗證使用者憑證的基本 Filter
。在可以驗證憑證之前,Spring Security 通常會使用 AuthenticationEntryPoint
請求憑證。
接下來,AbstractAuthenticationProcessingFilter
可以驗證提交給它的任何驗證請求。

當使用者提交其憑證時,
AbstractAuthenticationProcessingFilter
會從要驗證的 HttpServletRequest
建立 Authentication
。建立的 Authentication
類型取決於 AbstractAuthenticationProcessingFilter
的子類別。例如,UsernamePasswordAuthenticationFilter
會從 HttpServletRequest
中提交的使用者名稱和密碼建立 UsernamePasswordAuthenticationToken
。
接下來,
Authentication
會傳遞到 AuthenticationManager
進行驗證。
如果驗證失敗,則為失敗。
-
SecurityContextHolder
已清除。 -
會叫用
RememberMeServices.loginFail
。如果未組態記住我功能,則這是一個 no-op。請參閱rememberme
套件。 -
會叫用
AuthenticationFailureHandler
。請參閱AuthenticationFailureHandler
介面。
如果驗證成功,則為成功。
-
SessionAuthenticationStrategy
會收到新的登入通知。請參閱SessionAuthenticationStrategy
介面。 -
Authentication 會設定在 SecurityContextHolder 上。稍後,如果您需要儲存
SecurityContext
,以便可以在未來的請求上自動設定它,則必須明確叫用SecurityContextRepository#saveContext
。請參閱SecurityContextHolderFilter
類別。 -
會叫用
RememberMeServices.loginSuccess
。如果未組態記住我功能,則這是一個 no-op。請參閱rememberme
套件。 -
ApplicationEventPublisher
發佈InteractiveAuthenticationSuccessEvent
。 -
會叫用
AuthenticationSuccessHandler
。請參閱AuthenticationSuccessHandler
介面。