1. Spring Security 簡介
在 Spring 生態系統中,為他的項目增加安全性,你可以借助 Spring Security 庫來做到這一點。
那什么是 Spring Security?
從本質上講,Spring Security 實際上只是一堆 servlet 過濾器,可幫助您向Web應用程序添加身份驗證和授權。還與 Spring Web MVC(或 Spring Boot)之類的框架以及 OAuth2 或SAML 之類的標准很好地集成。並且它會自動生成登錄/注銷頁面,並防御 CSRF 等常見漏洞。
學習 Spring Security 前需要了解三個重要概念:
-
Authentication(認證)
-
Authorization(授權)
-
Servlet Filters(一系列 Servlet 過濾器)
1)認證
首先,如果您正在運行典型的(網絡)應用程序,則需要你的用戶進行身份驗證。這意味着您的應用需求,以驗證用戶是否是誰,他聲稱自己是,通常與一個用戶名和密碼檢查來完成。
Authentication 主要構件:
-
SecurityContextHolder:Spring Security在此處存儲經過身份驗證的人員的詳細信息。
-
SecurityContext:從 SecurityContextHolder 中獲取
,
並包含 Authentication 當前經過身份驗證的用戶的。 -
Authentication:可以是 AuthenticationManager 的輸入,以提供用戶提供的用於身份驗證的憑據或來自 SecurityContext 的當前用戶。
-
GrantedAuthority:在身份驗證的基礎上授予委托人的權限(即角色,作用域等)
-
AuthenticationManager:定義 Spring Security 的過濾器如何執行身份驗證的 API。
-
ProviderManager:最常見的實現 AuthenticationManager。
-
AuthenticationProvider:由 ProviderManager 用於執行特定類型的身份驗證。
-
使用 AuthenticationEntryPoint 的請求憑據:用於從客戶端請求憑據(即,重定向到登錄頁面,發送 WWW-Authenticate 響應等)
-
AbstractAuthenticationProcessingFilter: 用於認證的基礎 Filter 。這也為高級別的身份驗證流程以及各個部分如何協同工作提供了一個好主意。
認證機制:
-
用戶名和密碼:如何使用用戶名/密碼進行身份驗證
-
OAuth 2.0登錄:使用 OpenID Connect 和非標准 OAuth 2.0 登錄(即 GitHub)登錄的 OAuth 2.0
-
SAML 2.0登錄:SAML 2.0 登錄
-
中央身份驗證服務器(CAS):中央身份驗證服務器(CAS)支持
-
Remember Me:記住用戶過期的會話的功能
-
JAAS認證:使用 JAAS 進行認證
-
OpenID:OpenID 身份驗證(請勿與 OpenID Connect 混淆)
-
預先身份驗證方案(Pre-Authentication Scenarios):使用諸如 SiteMinder 或 Java EE 安全性之類的外部機制進行身份驗證,但仍使用 Spring Security 進行授權和防范常見漏洞利用。
-
X509驗證:X509 驗證
2. Authentication 主要構件介紹
① SecurityContextHolder
Spring Security 身份驗證模型的核心是 SecurityContextHolder。它包含 SecurityContext。
SecurityContextHolder 用於存儲通過身份驗證的人員的詳細信息。
如下代碼所示,指示用戶已通過身份驗證的最簡單方法是直接設置 SecurityContextHolder :
1 SecurityContext context = SecurityContextHolder.createEmptyContext(); 2 Authentication authentication = 3 new TestingAuthenticationToken("username", "password", "ROLE_USER"); 4 context.setAuthentication(authentication); 5 6 SecurityContextHolder.setContext(context);
如果你希望獲取有關已認證主體的信息,可以通過以下方式訪問 SecurityContextHolder 來獲得。
1 SecurityContext context = SecurityContextHolder.getContext(); 2 Authentication authentication = context.getAuthentication(); 3 String username = authentication.getName(); 4 Object principal = authentication.getPrincipal(); 5 Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
② SecurityContext 安全上下文
在 SecurityContextHolder 中所得 SecurityContext 中。該 SecurityContext 包含認證對象。
③ Authentication
它們 Authentication 在 Spring Security 中有兩個主要目的:
-
AuthenticationManager 用於提供用戶已提供身份驗證的憑據的輸入。在這種情況下使用時, isAuthenticated() 返回 false。
-
代表當前經過身份驗證的用戶。當前 Authentication 可以從 SecurityContext 獲得。
該 Authentication 包含:
-
principal:識別用戶。使用用戶名/密碼進行身份驗證時,這通常是的實例 UserDetails 。
-
credentials:通常是密碼。在許多情況下,將在驗證用戶身份后清除此內容,以確保它不會泄漏。
-
authorities:在 GrantedAuthority 是授予用戶的高級別權限。比如說角色或范圍。
④ GrantedAuthority 授予的權限
GrantedAuthority 是用戶將被授予的高級別權限。比如說角色或范圍。
GrantedAuthority 可以從該 Authentication.getAuthorities() 方法獲得。此方法提供了一個 GrantedAuthority 集合對象。GrantedAuthority 是授予某個主體的權限。此類權限通常是“角色”,例如 ROLE_ADMINISTRATOR 或 ROLE_HR_SUPERVISOR 。稍后將這些角色配置為 Web授權,方法授權和域對象授權。Spring Security 的其他部分能夠解釋這些權限,並希望它們存在。使用基於用戶名/密碼的身份驗證時GrantedAuthority
,通常會由加載 UserDetailsService 。
通常,GrantedAuthority 對象是應用程序范圍的權限。它們不特定於給定的域對象。因此,您不可能 GrantedAuthority 代表 Employee 54號對象的權限,因為如果有成千上萬個這樣的權限,這將很快用完內存(或者至少導致應用程序花費很長時間來完成驗證用戶身份)。當然,Spring Security是專門為滿足這一通用要求而設計的,但你可以為此目的使用項目的域對象安全性功能。
⑤ AuthenticationManager 認證管理器
AuthenticationManager 定義 Spring Security 的過濾器是如何執行身份驗證的 API 。然后再由調用 AuthenticationManager 的控制器(即 Spring Security 的 Filters)在SecurityContextHolder 上設置返回的Authentication。如果您不與Spring Security的過濾器集成,則可以直接設置 SecurityContextHolder,並且不需要使用 AuthenticationManager。
盡管的實現 AuthenticationManager 可以是任何對象,但最常見的實現是 ProviderManager。
⑥ ProviderManager
ProviderManager 是 AuthenticationManager 的最常見實現。ProviderManager 委托給 AuthenticationProvider 列表。 每個 AuthenticationProvider 都有機會指示認證應該成功,失敗,或者表明它不能做出決定並允許下游 AuthenticationProvider 進行決定。 如果沒有配置的 AuthenticationProviders 可以進行身份驗證,則身份驗證將失敗,並顯示ProviderNotFoundException,這是一個特殊的 AuthenticationException,它指示未配置 ProviderManager 支持傳遞給它的身份驗證類型。
實際上,每個 AuthenticationProvider 都知道如何執行特定類型的身份驗證。 例如,一個 AuthenticationProvider 可能能夠驗證用戶名/密碼,而另一個可能能夠驗證 SAML 斷言。 這允許每個 AuthenticationProvider 進行非常特定類型的身份驗證,同時支持多種類型的身份驗證,並且僅公開一個單例 AuthenticationManager bean。
ProviderManager 還允許配置可選的父類 AuthenticationManager,如果沒有 AuthenticationProvider 可以執行身份驗證,請咨詢該父對象。 父級可以是任何類型的AuthenticationManager,但通常是 ProviderManager的實例。
實際上,多個 ProviderManager 實例可能共享同一個父類 AuthenticationManager。 在存在多個具有相同身份驗證(共享的父類 AuthenticationManager)但又具有不同身份驗證機制(不同 ProviderManager 實例)的多個 SecurityFilterChain 實例的情況下,這種情況有些常見。
默認情況下,ProviderManager 會嘗試清除身份驗證對象中所有敏感的憑據信息,這些信息將由成功的身份驗證請求返回。 這樣可以防止密碼之類的信息在 HttpSession 中的保留時間超過所需的時間。
例如,在使用用戶對象的緩存來提高無狀態應用程序的性能時,這可能會導致問題。 如果身份驗證包含對緩存中某個對象(例如 UserDetails 實例)的引用,並且已刪除其憑據,則將無法再對緩存的值進行身份驗證。 如果使用緩存,則需要考慮到這一點。 一個明顯的解決方案是首先在緩存實現中或在創建返回的 Authentication 對象的 AuthenticationProvider 中創建對象的副本。 或者,你可以在 ProviderManager 上禁用 deleteCredentialsAfterAuthentication 屬性。
⑦ AuthenticationProvider
可以將多個 AuthenticationProvider 注入 ProviderManager。 每個 AuthenticationProvider 執行特定類型的身份驗證。 例如,DaoAuthenticationProvider 支持基於用戶名/密碼的身份驗證,而 JwtAuthenticationProvider 支持對JWT令牌的身份驗證。AuthenticationEntryPoint
⑧ 使用 AuthenticationEntryPoint 的請求憑據
AuthenticationEntryPoint 用於發送請求憑據響應,以回應客戶端 HTTP 認證。有時,客戶端會主動包含憑據(例如用戶名/密碼)以請求資源。 在這些情況下,Spring Security 不需要提供 HTTP 響應來從客戶端請求憑據,因為它們已經包含在內。
在其他情況下,客戶端將對未經授權訪問的資源發出未經身份驗證的請求。 在這種情況下,AuthenticationEntryPoint 的實現用於從客戶端請求憑據。 AuthenticationEntryPoint 實現可能會執行重定向到登錄頁面,使用 WWW-Authenticate 標頭進行響應等。
⑨ AbstractAuthenticationProcessingFilter
AbstractAuthenticationProcessingFilter 用作驗證用戶憑據的基本過濾器。 在對憑證進行身份驗證之前,Spring Security 通常使用 AuthenticationEntryPoint 請求憑證。
接下來,AbstractAuthenticationProcessingFilter 可以對提交給它的任何身份驗證請求進行身份驗證。
1. 當用戶提交其憑據時,AbstractAuthenticationProcessingFilter 從要驗證的 HttpServletRequest 創建一個 Authentication。創建的身份驗證類型取決於 AbstractAuthenticationProcessingFilter 的子類。例如,UsernamePasswordAuthenticationFilter 根據在 HttpServletRequest 中提交的用戶名和密碼來創建UsernamePasswordAuthenticationToken。
2. 接下來,將身份驗證傳遞到AuthenticationManager進行身份驗證。
3. 如果身份驗證失敗,則失敗事件有:
已清除 SecurityContextHolder。
RememberMeServices.loginFail 被調用。如果 RememberMe 未配置,則為空。
AuthenticationFailureHandler 被調用。
4. 如果身份驗證成功,則成功事件有:
SessionAuthenticationStrategy 會通知有新的登錄。
身份驗證是在SecurityContextHolder上設置的。 之后,SecurityContextPersistenceFilter 將 SecurityContext 保存到 HttpSession中。
RememberMeServices.loginSuccess 被調用。 如果 RememberMe 未配置,則為空。
ApplicationEventPublisher 發布一個 InteractiveAuthenticationSuccessEvent。
⑩ Username/Password Authentication
驗證用戶身份的最常見方法之一是驗證用戶名和密碼。這樣,Spring Security 為使用用戶名和密碼進行身份驗證提供了全面的支持。
Spring Security 提供了以下內置機制,用於從 HttpServletRequest 中讀取用戶名和密碼:
-
Form Login(表單登入)
-
Basic Authentication(基本認證)
-
Digest Authentication(摘要式身份驗證)
用於讀取用戶名和密碼的每種受支持的機制都可以利用任何受支持的存儲機制:
- 具有內存內認證的簡單存儲(In-Memory Authentication)
- 具有 JDBC 身份驗證的關系數據庫(JDBC Authentication)
- 使用 UserDetailsService 的自定義數據存儲(use UserDetails)
- 具有 LDAP 認證的 LDAP 存儲(LDAP Authentication)
⑪ Session Management
與 HTTP 會話相關的功能由 SessionManagementFilter 和 SessionAuthenticationStrategy 接口的組合來處理,過濾器委托該接口。 典型的用法包括防止會話固定保護攻擊,檢測會話超時以及限制已認證用戶可以同時打開多少個會話。其功能有:
-
檢測超時
-
並發會話控制
-
會話固定攻擊防護
-
SessionManagementFilter
-
SessionAuthenticationStrategy
-
並發控制
⑫ Remember-Me 認證
“記住我”或“永久登錄”身份驗證是指能夠記住會話之間的主體身份的網站。 通常,這是通過向瀏覽器發送一個 cookie 來實現的,該 cookie 在以后的會話中被檢測到並導致自動登錄。 Spring Security 提供了進行這些操作所需的鈎子,並具有兩個具體的“記住我”實現。 一種使用散列來保留基於 cookie 的令牌的安全性,另一種使用數據庫或其他持久性存儲機制來存儲生成的令牌。這兩種實現都需要 UserDetailsService。
⑬ 注銷處理
使用 WebSecurityConfigurerAdapter 時,將自動應用注銷功能。 默認是訪問 URL / logout 將通過以下方式注銷用戶:
-
使 HTTP 會話無效
-
清理配置的所有 RememberMe 身份驗證
-
清除 SecurityContextHolder
-
重定向到 /login?logout
⑭ 認證事件
對於成功或失敗的每個身份驗證,分別觸發 AuthenticationSuccessEvent 或 AuthenticationFailureEvent。
若要偵聽這些事件,必須首先發布 AuthenticationEventPublisher。 Spring Security 的 DefaultAuthenticationEventPublisher 可能會很好:
1 @Bean 2 public AuthenticationEventPublisher authenticationEventPublisher 3 (ApplicationEventPublisher applicationEventPublisher) { 4 return new DefaultAuthenticationEventPublisher(applicationEventPublisher); 5 }
然后,你可以使用 Spring 的 @EventListener 支持:
1 @Component 2 public class AuthenticationEvents { 3 @EventListener 4 public void onSuccess(AuthenticationSuccessEvent success) { 5 // ... 6 } 7 8 @EventListener 9 public void onFailure(AuthenticationFailureEvent failures) { 10 // ... 11 } 12 }
盡管與 AuthenticationSuccessHandler 和 AuthenticationFailureHandler 相似,但它們的優點是可以獨立於Servlet API使用。
(etc.)
主要譯自:Spring Security官方文檔