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

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: 通常是密碼。在許多情況下,使用者認證後會清除此資訊,以確保其不被洩露。

  • authorities: GrantedAuthority 例項是授予使用者的頂級許可權。兩個例子是角色和作用域。

GrantedAuthority

GrantedAuthority 例項是授予使用者的頂級許可權。兩個例子是角色和作用域。

你可以從 Authentication.getAuthorities() 方法獲取 GrantedAuthority 例項。此方法提供一個 GrantedAuthority 物件集合。GrantedAuthority 顧名思義,是授予 principal 的許可權。這些許可權通常是“角色”,例如 ROLE_ADMINISTRATORROLE_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

ProviderManagerAuthenticationManager 最常用的實現。ProviderManager 委託給一個 AuthenticationProvider 列表。每個 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

number 1 當用戶提交憑證時,AbstractAuthenticationProcessingFilterHttpServletRequest 建立一個 Authentication 物件進行認證。建立的 Authentication 型別取決於 AbstractAuthenticationProcessingFilter 的子類。例如,UsernamePasswordAuthenticationFilterHttpServletRequest 中提交的 *username* 和 *password* 建立一個 UsernamePasswordAuthenticationToken.

number 2 接下來,Authentication 被傳遞到 AuthenticationManager 中進行認證。

number 3 如果身份驗證失敗,則執行 失敗

  • SecurityContextHolder 被清除。

  • RememberMeServices.loginFail 被呼叫。如果沒有配置 remember me,則這是一個無操作。請參閱 rememberme 包。

  • AuthenticationFailureHandler 被呼叫。請參閱 AuthenticationFailureHandler 介面。

number 4 如果身份驗證成功,則執行 成功

  • SessionAuthenticationStrategy 通知新登入。請參閱 SessionAuthenticationStrategy 介面。

  • Authentication 被設定到 SecurityContextHolder 上。稍後,如果你需要儲存 SecurityContext 以便在未來的請求中自動設定,必須顯式呼叫 SecurityContextRepository#saveContext. 請參閱 SecurityContextHolderFilter 類。

  • RememberMeServices.loginSuccess 被呼叫。如果沒有配置 remember me,則這是一個無操作。請參閱 rememberme 包。

  • ApplicationEventPublisher 釋出一個 InteractiveAuthenticationSuccessEvent.

  • AuthenticationSuccessHandler 被呼叫。請參閱 AuthenticationSuccessHandler 介面。