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 最後,我們在 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:通常是密碼。在許多情況下,這會在使用者通過驗證後清除,以確保不會洩漏。

  • authoritiesGrantedAuthority 執行個體是授與使用者的進階權限。兩個範例是角色和範圍。

GrantedAuthority

GrantedAuthority 執行個體是授與使用者的進階權限。兩個範例是角色和範圍。

您可以從 Authentication.getAuthorities() 方法取得 GrantedAuthority 執行個體。此方法提供 GrantedAuthority 物件的 CollectionGrantedAuthority 顧名思義,是授與主體的授權。此類授權通常是「角色」,例如 ROLE_ADMINISTRATORROLE_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

ProviderManagerAuthenticationManager 最常用的實作。ProviderManager 委派給 AuthenticationProvider 執行個體的 ListAuthenticationProvider 執行個體都有機會指示驗證應成功、失敗,或指示它無法做出決定,並允許下游 AuthenticationProvider 做出決定。如果沒有任何已組態的 AuthenticationProvider 執行個體可以驗證,則驗證會失敗,並出現 ProviderNotFoundException,這是一個特殊的 AuthenticationException,指示 ProviderManager 未組態為支援傳遞給它的 Authentication 類型。

providermanager

實際上,每個 AuthenticationProvider 都知道如何執行特定類型的驗證。例如,一個 AuthenticationProvider 可能能夠驗證使用者名稱/密碼,而另一個可能能夠驗證 SAML 斷言。這讓每個 AuthenticationProvider 都能執行非常特定類型的驗證,同時支援多種類型的驗證,並且僅公開單一 AuthenticationManager Bean。

ProviderManager 也允許組態選用的父系 AuthenticationManager,如果在沒有 AuthenticationProvider 可以執行驗證的情況下,將會諮詢父系 AuthenticationManager。父系可以是任何類型的 AuthenticationManager,但通常是 ProviderManager 的執行個體。

providermanager parent

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

providermanagers parent

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

當您使用使用者物件的快取 (例如,為了改善無狀態應用程式中的效能) 時,這可能會導致問題。如果 Authentication 包含對快取中物件 (例如 UserDetails 執行個體) 的參考,且此物件的憑證已移除,則不再可能針對快取值進行驗證。如果您使用快取,則需要考慮到這一點。一個顯而易見的解決方案是先建立物件的複本,無論是在快取實作中,還是在建立傳回的 Authentication 物件的 AuthenticationProvider 中。或者,您可以停用 ProviderManager 上的 eraseCredentialsAfterAuthentication 屬性。請參閱 ProviderManager 類別的 Javadoc。

AuthenticationProvider

您可以將多個 AuthenticationProviders 執行個體注入到 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 當使用者提交其憑證時,AbstractAuthenticationProcessingFilter 會從要驗證的 HttpServletRequest 建立 Authentication。建立的 Authentication 類型取決於 AbstractAuthenticationProcessingFilter 的子類別。例如,UsernamePasswordAuthenticationFilter 會從 HttpServletRequest 中提交的使用者名稱密碼建立 UsernamePasswordAuthenticationToken

number 2 接下來,Authentication 會傳遞到 AuthenticationManager 進行驗證。

number 3 如果驗證失敗,則為失敗

  • SecurityContextHolder 已清除。

  • 會叫用 RememberMeServices.loginFail。如果未組態記住我功能,則這是一個 no-op。請參閱 rememberme 套件。

  • 會叫用 AuthenticationFailureHandler。請參閱 AuthenticationFailureHandler 介面。

number 4 如果驗證成功,則為成功

  • SessionAuthenticationStrategy 會收到新的登入通知。請參閱 SessionAuthenticationStrategy 介面。

  • Authentication 會設定在 SecurityContextHolder 上。稍後,如果您需要儲存 SecurityContext,以便可以在未來的請求上自動設定它,則必須明確叫用 SecurityContextRepository#saveContext。請參閱 SecurityContextHolderFilter 類別。

  • 會叫用 RememberMeServices.loginSuccess。如果未組態記住我功能,則這是一個 no-op。請參閱 rememberme 套件。

  • ApplicationEventPublisher 發佈 InteractiveAuthenticationSuccessEvent

  • 會叫用 AuthenticationSuccessHandler。請參閱 AuthenticationSuccessHandler 介面。