持久化認證
GET / HTTP/1.1
Host: example.com
Cookie: SESSION=91470ce0-3f3c-455b-b7ad-079b02290f7b
HTTP/1.1 302 Found
Location: /login
使用者提交其使用者名稱和密碼。
POST /login HTTP/1.1
Host: example.com
Cookie: SESSION=91470ce0-3f3c-455b-b7ad-079b02290f7b
username=user&password=password&_csrf=35942e65-a172-4cd4-a1d4-d16a51147b3e
認證使用者後,會將使用者關聯到一個新的會話 ID,以防止會話固定攻擊。
HTTP/1.1 302 Found
Location: /
Set-Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8; Path=/; HttpOnly; SameSite=Lax
後續請求包含會話 cookie,該 cookie 用於在會話的剩餘時間內認證使用者。
GET / HTTP/1.1
Host: example.com
Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8
SecurityContextRepository
在 Spring Security 中,使用者與未來請求的關聯是透過SecurityContextRepository進行的。SecurityContextRepository的預設實現是DelegatingSecurityContextRepository,它委託給以下物件:
HttpSessionSecurityContextRepository
HttpSessionSecurityContextRepository將SecurityContext關聯到HttpSession。如果使用者希望以其他方式或完全不將使用者與後續請求關聯,則可以替換HttpSessionSecurityContextRepository為SecurityContextRepository的另一個實現。
NullSecurityContextRepository
如果不需要將SecurityContext關聯到HttpSession(即使用 OAuth 認證時),則NullSecurityContextRepository是SecurityContextRepository的一個實現,它不執行任何操作。
RequestAttributeSecurityContextRepository
RequestAttributeSecurityContextRepository將SecurityContext儲存為請求屬性,以確保SecurityContext在可能清除SecurityContext的排程型別中可用於單個請求。
例如,假設客戶端發出請求,被認證,然後發生錯誤。根據 Servlet 容器的實現,錯誤意味著任何已建立的SecurityContext都會被清除,然後進行錯誤排程。當進行錯誤排程時,沒有建立SecurityContext。這意味著錯誤頁面無法使用SecurityContext進行授權或顯示當前使用者,除非SecurityContext以某種方式持久化。
-
Java
-
XML
public SecurityFilterChain filterChain(HttpSecurity http) {
http
// ...
.securityContext((securityContext) -> securityContext
.securityContextRepository(new RequestAttributeSecurityContextRepository())
);
return http.build();
}
<http security-context-repository-ref="contextRepository">
<!-- ... -->
</http>
<b:bean name="contextRepository"
class="org.springframework.security.web.context.RequestAttributeSecurityContextRepository" />
DelegatingSecurityContextRepository
DelegatingSecurityContextRepository將SecurityContext儲存到多個SecurityContextRepository委託中,並允許以指定順序從任何委託中檢索。
最常用的配置如下例所示,它允許同時使用RequestAttributeSecurityContextRepository和HttpSessionSecurityContextRepository。
-
Java
-
Kotlin
-
XML
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ...
.securityContext((securityContext) -> securityContext
.securityContextRepository(new DelegatingSecurityContextRepository(
new RequestAttributeSecurityContextRepository(),
new HttpSessionSecurityContextRepository()
))
);
return http.build();
}
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
securityContext {
securityContextRepository = DelegatingSecurityContextRepository(
RequestAttributeSecurityContextRepository(),
HttpSessionSecurityContextRepository()
)
}
}
return http.build()
}
<http security-context-repository-ref="contextRepository">
<!-- ... -->
</http>
<bean name="contextRepository"
class="org.springframework.security.web.context.DelegatingSecurityContextRepository">
<constructor-arg>
<bean class="org.springframework.security.web.context.RequestAttributeSecurityContextRepository" />
</constructor-arg>
<constructor-arg>
<bean class="org.springframework.security.web.context.HttpSessionSecurityContextRepository" />
</constructor-arg>
</bean>
|
在 Spring Security 6 中,上面顯示的示例是預設配置。 |
SecurityContextPersistenceFilter
SecurityContextPersistenceFilter負責使用SecurityContextRepository在請求之間持久化SecurityContext。
在執行應用程式的其餘部分之前,SecurityContextPersistenceFilter從SecurityContextRepository載入SecurityContext並將其設定到SecurityContextHolder上。
接下來,執行應用程式。
最後,如果SecurityContext已更改,我們將使用SecurityContextRepository儲存SecurityContext。這意味著當使用SecurityContextPersistenceFilter時,只需設定SecurityContextHolder即可確保SecurityContext使用SecurityContextRepository持久化。
在某些情況下,響應在SecurityContextPersistenceFilter方法完成之前被提交併寫入客戶端。例如,如果重定向傳送到客戶端,則響應會立即寫入客戶端。這意味著在步驟 3 中無法建立HttpSession,因為會話 ID 無法包含在已寫入的響應中。可能發生的另一種情況是,如果客戶端成功認證,則響應在SecurityContextPersistenceFilter完成之前提交,並且客戶端在SecurityContextPersistenceFilter完成之前發出第二個請求。則第二個請求中可能存在錯誤的認證。
為避免這些問題,SecurityContextPersistenceFilter包裝了HttpServletRequest和HttpServletResponse,以檢測SecurityContext是否已更改,如果更改,則在響應提交之前儲存SecurityContext。
SecurityContextHolderFilter
SecurityContextHolderFilter負責使用SecurityContextRepository在請求之間載入SecurityContext。
在執行應用程式的其餘部分之前,SecurityContextHolderFilter從SecurityContextRepository載入SecurityContext並將其設定到SecurityContextHolder上。
接下來,執行應用程式。
與SecurityContextPersistenceFilter不同,SecurityContextHolderFilter只加載SecurityContext,它不儲存SecurityContext。這意味著當使用SecurityContextHolderFilter時,需要顯式儲存SecurityContext。
-
Java
-
Kotlin
-
XML
public SecurityFilterChain filterChain(HttpSecurity http) {
http
// ...
.securityContext((securityContext) -> securityContext
.requireExplicitSave(true)
);
return http.build();
}
@Bean
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
http {
securityContext {
requireExplicitSave = true
}
}
return http.build()
}
<http security-context-explicit-save="true">
<!-- ... -->
</http>
在使用此配置時,任何使用SecurityContext設定SecurityContextHolder的程式碼也必須將SecurityContext儲存到SecurityContextRepository中,如果它需要在請求之間持久化。
例如,以下程式碼
SecurityContextPersistenceFilter 設定 SecurityContextHolder-
Java
-
Kotlin
SecurityContextHolder.setContext(securityContext);
SecurityContextHolder.setContext(securityContext)
應該替換為
SecurityContextHolderFilter 設定 SecurityContextHolder-
Java
-
Kotlin
SecurityContextHolder.setContext(securityContext);
securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse);
SecurityContextHolder.setContext(securityContext)
securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse)