Servlet 身份驗證架構
本文件基於 Servlet 安全:全域性檢視 的基礎上,擴充套件描述了 Spring Security 在 Servlet 身份驗證中使用的主要架構元件。如果你需要具體的流程來解釋這些元件如何協同工作,請檢視 身份驗證機制 特定章節。
-
SecurityContextHolder -
SecurityContextHolder
是 Spring Security 儲存已認證主體詳細資訊的地方。 -
SecurityContext - 從
SecurityContextHolder
獲取,包含當前已認證使用者的Authentication
。 -
Authentication - 可以作為
AuthenticationManager
的輸入,提供使用者用於認證的憑證;也可以是SecurityContext
中當前使用者的資訊。 -
GrantedAuthority - 授予給
Authentication
上的 principal 的許可權(例如角色、作用域等) -
AuthenticationManager - 定義 Spring Security 的 Filter 如何執行 身份驗證 的 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 | 最後,我們將 SecurityContext 設定到 SecurityContextHolder 上。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
物件集合。GrantedAuthority
顧名思義,是授予 principal 的許可權。這些許可權通常是“角色”,例如 ROLE_ADMINISTRATOR
或 ROLE_HR_SUPERVISOR
. 這些角色稍後會用於配置 Web 授權、方法授權和領域物件授權。Spring Security 的其他部分會解析這些許可權,並期望它們存在。使用基於使用者名稱/密碼的身份驗證時,GrantedAuthority
例項通常由 UserDetailsService
載入。
通常,GrantedAuthority
物件是應用程式範圍內的許可權。它們不特定於某個領域物件。因此,你不太可能會有一個 GrantedAuthority
來表示對員工物件編號 54 的許可權,因為如果這樣的許可權有數千個,你很快就會耗盡記憶體(或者至少會導致應用程式認證使用者所需時間過長)。當然,Spring Security 明確設計了處理這一常見需求的功能,但你應該轉而使用該專案的領域物件安全能力來實現此目的。
AuthenticationManager
AuthenticationManager
是定義 Spring Security 的 Filter 如何執行 身份驗證 的 API。AuthenticationManager
返回的 Authentication
隨後由呼叫 AuthenticationManager
的控制器(即 Spring Security 的 Filter 例項)設定到 SecurityContextHolder 上。如果你沒有整合 Spring Security 的 Filter 例項,你可以直接設定 SecurityContextHolder
,而不需要使用 AuthenticationManager
.
雖然 AuthenticationManager
的實現可以是任何型別,但最常見的實現是 ProviderManager
.
ProviderManager
ProviderManager
是 AuthenticationManager
最常用的實現。ProviderManager
委託給一個 AuthenticationProvider
列表。每個 AuthenticationProvider
都有機會表明身份驗證應該成功、失敗,或者表明它無法做出決定,並允許下游的 AuthenticationProvider
做出決定。如果配置的所有 AuthenticationProvider
例項都無法進行身份驗證,則身份驗證將失敗並丟擲 ProviderNotFoundException
,這是一個特殊的 AuthenticationException
,表示 ProviderManager
沒有配置來支援傳遞給它的 Authentication
型別。

實際上,每個 AuthenticationProvider
都知道如何執行特定型別的身份驗證。例如,一個 AuthenticationProvider
可能能夠驗證使用者名稱/密碼,而另一個可能能夠驗證 SAML 斷言。這使得每個 AuthenticationProvider
可以執行非常特定型別的身份驗證,同時支援多種身份驗證型別並僅暴露一個 AuthenticationManager
bean。
ProviderManager
還允許配置一個可選的父級 AuthenticationManager
,當沒有 AuthenticationProvider
可以執行身份驗證時,會諮詢該父級。父級可以是任何型別的 AuthenticationManager
,但它通常是 ProviderManager
的一個例項。

實際上,多個 ProviderManager
例項可以共享同一個父級 AuthenticationManager
. 這在存在多個 SecurityFilterChain
例項,並且它們有一些共同的身份驗證(共享的父級 AuthenticationManager
),但也有不同的身份驗證機制(不同的 ProviderManager
例項)的場景中很常見。

預設情況下,ProviderManager
會嘗試清除成功身份驗證請求返回的 Authentication
物件中的任何敏感憑證資訊。這可以防止密碼等資訊在 HttpSession
中不必要地長時間保留。
|
當你在無狀態應用程式中使用使用者物件快取(例如為了提高效能)時,這可能會導致問題。如果 Authentication
包含對快取中物件(例如 UserDetails
例項)的引用,並且其憑證被移除,那麼將無法再使用快取的值進行身份驗證。如果你使用快取,你需要考慮這一點。一個明顯的解決方案是,要麼在快取實現中,要麼在建立返回的 Authentication
物件的 AuthenticationProvider
中,首先複製該物件。或者,你可以停用 ProviderManager
上的 eraseCredentialsAfterAuthentication
屬性。請參閱 ProviderManager
類的 Javadoc。
AuthenticationProvider
你可以將多個 AuthenticationProvider
例項注入到 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
中提交的 *username* 和 *password* 建立一個 UsernamePasswordAuthenticationToken
.
接下來,
Authentication
被傳遞到 AuthenticationManager
中進行認證。
如果身份驗證失敗,則執行 失敗。
-
SecurityContextHolder
被清除。 -
RememberMeServices.loginFail
被呼叫。如果沒有配置 remember me,則這是一個無操作。請參閱 rememberme 包。 -
AuthenticationFailureHandler
被呼叫。請參閱AuthenticationFailureHandler
介面。
如果身份驗證成功,則執行 成功。
-
SessionAuthenticationStrategy
通知新登入。請參閱SessionAuthenticationStrategy
介面。 -
Authentication
被設定到 SecurityContextHolder 上。稍後,如果你需要儲存SecurityContext
以便在未來的請求中自動設定,必須顯式呼叫SecurityContextRepository#saveContext
. 請參閱SecurityContextHolderFilter
類。 -
RememberMeServices.loginSuccess
被呼叫。如果沒有配置 remember me,則這是一個無操作。請參閱 rememberme 包。 -
ApplicationEventPublisher
釋出一個InteractiveAuthenticationSuccessEvent
. -
AuthenticationSuccessHandler
被呼叫。請參閱AuthenticationSuccessHandler
介面。