Servlet 認證架構

本討論在 Servlet 安全:全域性概覽 的基礎上,描述了 Spring Security 在 Servlet 認證中使用的主要架構元件。如果您需要具體的流程來解釋這些元件如何協同工作,請參閱 認證機制 特定章節。

SecurityContextHolder

Spring Security 認證模型的核心是 SecurityContextHolder。它包含 SecurityContext

securitycontextholder

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

SecurityContextSecurityContextHolder 獲取。SecurityContext 包含一個 Authentication 物件。

Authentication

Authentication 介面在 Spring Security 中主要有兩個用途

  • 作為 AuthenticationManager 的輸入,提供使用者提供的憑據以進行認證。在這種情況下,isAuthenticated() 返回 false

  • 表示當前已認證使用者。您可以從 SecurityContext 獲取當前的 Authentication

Authentication 包含

  • principal:標識使用者。使用使用者名稱/密碼進行認證時,這通常是 UserDetails 的例項。

  • credentials:通常是密碼。在許多情況下,使用者認證後會清除此項,以確保其不會洩露。

  • authoritiesGrantedAuthority 例項是授予使用者的高階許可權。兩個示例是角色和範圍。

它還配備了一個 AdditionalRequiredFactorsBuilder,允許您修改現有的 Authentication 例項並可能將其與另一個合併。這在需要從一個認證步驟(如表單登入)獲取許可權並將其應用於另一個認證步驟(如一次性令牌登入)的場景中非常有用,如下所示

  • Java

  • Kotlin

Authentication lastestResult = authenticationManager.authenticate(authenticationRequest);
Authentication previousResult = SecurityContextHolder.getContext().getAuthentication();
if (previousResult != null && previousResult.isAuthenticated()) {
	lastestResult = lastestResult.toBuilder()
			.authorities((a) -> a.addAll(previous.getAuthorities()))
			.build();
}
var latestResult: Authentication = authenticationManager.authenticate(authenticationRequest)
val previousResult = SecurityContextHolder.getContext().authentication;
if (previousResult?.isAuthenticated == true) {
    latestResult = latestResult.toBuilder().authorities { a ->
        a.addAll(previousResult.authorities)
    }.build()
}

GrantedAuthority

GrantedAuthority 例項是授予使用者的高階許可權。兩個示例是角色和範圍。

您可以從 Authentication.getAuthorities() 方法獲取 GrantedAuthority 例項。此方法提供一個 GrantedAuthority 物件的 CollectionGrantedAuthority 顧名思義,是授予主體的許可權。此類許可權通常是“角色”,例如 ROLE_ADMINISTRATORROLE_HR_SUPERVISOR。這些角色隨後用於 Web 授權、方法授權和領域物件授權。Spring Security 的其他部分會解釋這些許可權並期望它們存在。在使用基於使用者名稱/密碼的認證時,GrantedAuthority 例項通常由 UserDetailsService 載入。

通常,GrantedAuthority 物件是應用程式範圍的許可權。它們不特定於給定的領域物件。因此,您不太可能擁有一個 GrantedAuthority 來表示對 Employee 物件編號 54 的許可權,因為如果有數千個這樣的許可權,您很快就會耗盡記憶體(或者,至少會導致應用程式認證使用者花費很長時間)。當然,Spring Security 明確設計用於處理這種常見需求,但您應該為此目的使用專案的領域物件安全功能。

AuthenticationManager

AuthenticationManager 是定義 Spring Security 過濾器如何執行 認證 的 API。返回的 Authentication 隨後由呼叫 AuthenticationManager 的控制器(即 Spring Security 的 Filters 例項)設定到 SecurityContextHolder 上。如果您沒有與 Spring Security 的 Filters 例項整合,您可以直接設定 SecurityContextHolder,並且不需要使用 AuthenticationManager

雖然 AuthenticationManager 的實現可以是任何型別,但最常見的實現是 ProviderManager

ProviderManager

ProviderManagerAuthenticationManager 最常用的實現。ProviderManager 將認證委託給一個 AuthenticationProvider 例項的 List。每個 AuthenticationProvider 都有機會指示認證應該成功、失敗,或者指示它無法做出決定並允許下游的 AuthenticationProvider 做出決定。如果沒有一個配置的 AuthenticationProvider 例項能夠進行認證,則認證失敗,並丟擲 ProviderNotFoundException,這是一個特殊的 AuthenticationException,表示 ProviderManager 未配置為支援傳遞給它的 Authentication 型別。

providermanager

實際上,每個 AuthenticationProvider 都知道如何執行特定型別的認證。例如,一個 AuthenticationProvider 可能能夠驗證使用者名稱/密碼,而另一個可能能夠認證 SAML 斷言。這使得每個 AuthenticationProvider 都能執行非常特定型別的認證,同時支援多種認證型別並僅暴露一個 AuthenticationManager bean。

ProviderManager 還允許配置一個可選的父級 AuthenticationManager,當沒有任何 AuthenticationProvider 能夠執行認證時,將諮詢該父級。父級可以是任何型別的 AuthenticationManager,但它通常是 ProviderManager 的一個例項。

providermanager parent

事實上,多個 ProviderManager 例項可能共享同一個父 AuthenticationManager。這在存在多個 SecurityFilterChain 例項,它們具有一些共同的認證(共享的父 AuthenticationManager),但也有不同的認證機制(不同的 ProviderManager 例項)的場景中相當常見。

providermanagers parent

預設情況下,ProviderManager 會嘗試清除成功認證請求返回的 Authentication 物件中的任何敏感憑據資訊。這可以防止密碼等資訊在 HttpSession 中保留的時間超過必要。

CredentialsContainer 介面在認證過程中扮演著關鍵角色。它允許在不再需要憑據資訊時將其擦除,從而透過確保敏感資料不會保留超出必要時間來增強安全性。

當您使用使用者物件快取時(例如,為了提高無狀態應用程式的效能),這可能會導致問題。如果 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

數字 1 當用戶提交憑據時,AbstractAuthenticationProcessingFilterHttpServletRequest 建立一個 Authentication 以進行認證。建立的 Authentication 型別取決於 AbstractAuthenticationProcessingFilter 的子類。例如,UsernamePasswordAuthenticationFilterHttpServletRequest 中提交的 *使用者名稱* 和 *密碼* 建立一個 UsernamePasswordAuthenticationToken

數字 2 接下來,將 Authentication 傳遞給 AuthenticationManager 進行認證。

數字 3 如果認證失敗,則為 *失敗*。

數字 4 如果認證成功,則為 *成功*。

© . This site is unofficial and not affiliated with VMware.