一、問題背景
基於上一篇文章修復登錄警告:SPRING_SECURITY_CONTEXT 未包含 SecurityContext 的原因與解決方案,我們主要講述了錯在哪裏,如何改正,改正後為何不再報錯。而本篇文章則會重點探討:
為什麼Session中必須放SecurityContext而不能直接放Authentication?
核心原因在於:Spring Security的整個認證機制是圍繞過濾器鏈運行的。
理解過濾器鏈,你就能徹底理解本次問題的根源和修復原理。
二、Spring Security 過濾器鏈的核心作用
Spring Security 的認證授權機制本質上是一個過濾器鏈,其核心作用是構建完整的安全防護體系。這個體系通過多個過濾器的協同工作,實現了從請求接收到響應返回的全流程安全控制。
2.1 過濾器鏈的核心價值
- 分層防護:每個過濾器專注於特定的安全領域,如CSRF防護、認證、授權等
- 責任分離:不同的安全 concerns 由不同的過濾器處理,代碼清晰易維護
- 可擴展性:可以方便地添加、移除或替換特定的安全過濾器
- 統一入口:所有請求都經過相同的安全檢查和防護
三、深入理解Spring Security 過濾器鏈架構
3.1 從 Servlet Filter 到 Spring Security
在標準的 Servlet 應用中,過濾器鏈是這樣的:
客户端請求→Servlet Filter 1→Servlet Filter 2→...→DispatcherServlet
Spring Security 在此基礎上構建了自己的過濾器體系:
3.2 關鍵組件解析
3.2.1 DelegatingFilterProxy:連接 Servlet 容器與 Spring 容器的橋樑
這個組件解決了 Servlet 容器與 Spring 容器的集成問題:
Servlet容器層面:作為普通的Servlet Filter註冊Spring容器層面:將實際工作委託給Spring管理的Filter Bean- 延遲加載:支持在
Spring容器完全啓動後再獲取Filter實例
3.2.2 FilterChainProxy:Spring Security 的核心
這是 Spring Security 的真正入口,提供:
- 統一管理:所有安全過濾器的調度中心
- 靈活匹配:根據請求路徑選擇不同的安全策略
- 內存管理:自動清理
SecurityContext,防止內存泄漏 - 防火牆保護:內置
HttpFirewall防護常見攻擊
3.2.3 SecurityFilterChain:靈活的安全配置
通過配置多個 SecurityFilterChain,可以實現不同路徑的不同安全策略:
@Bean
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**") // 只匹配 /api 路徑
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.httpBasic(Customizer.withDefaults());
return http.build();
}
@Bean
public SecurityFilterChain webFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/**") // 匹配所有路徑
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll());
return http.build();
}
四、SecurityContext:貫穿認證流程的核心載體
4.1 為什麼必須是 SecurityContext?
**核心原因:SecurityContext 是連接各個安全過濾器的"上下文總線"。
**
讓我們通過一個具體的請求流程來理解:
4.2 SecurityContext 的完整職責
4.2.1 線程安全的認證信息管理
// SecurityContextHolder 使用 ThreadLocal 保證線程安全
public class SecurityContextHolder {
private static SecurityContextStrategy strategy;
public static SecurityContext getContext() {
return strategy.getContext();
}
public static void setContext(SecurityContext context) {
strategy.setContext(context);
}
}
每個請求線程都有自己獨立的 SecurityContext,確保多用户併發訪問時的數據隔離。
4.2.2 統一的認證信息訪問接口
SecurityContext 提供了標準化的方式來訪問和修改認證信息:
public interface SecurityContext extends Serializable {
// 獲取當前認證信息
Authentication getAuthentication();
// 設置認證信息(登錄成功時調用)
void setAuthentication(Authentication authentication);
}
4.2.3 支持複雜的認證狀態管理
與直接存儲 Authentication 相比,SecurityContext 能夠處理更復雜的場景,但本文暫不贅述。
五、調試與問題排查
5.1 啓用安全日誌
在 application.yml 中配置:
logging:
level:
org.springframework.security: DEBUG # 查看安全過濾器執行過程
org.springframework.security.web.FilterChainProxy: TRACE # 查看詳細的過濾器調用鏈
5.2 理解日誌輸出
根據真實日誌,我們可以分析 Spring Security 過濾器鏈的實際執行過程:
注:不同版本的Spring Security相關過濾器的執行過程有所不同。
5.2.1 登錄請求的完整過濾器鏈執行
- 過濾器鏈匹配
- 開始處理
POST /user/login請求 - 按順序執行
10個安全過濾器 - 過濾器鏈執行完成
5.2.2 認證成功後的 SecurityContext 存儲
- 用户認證成功
SecurityContext被保存到Session
5.2.3 後續請求的安全上下文恢復
- 後續請求自動恢復
SecurityContext SecurityContextHolderFilter自動從Session恢復用户認證信息
5.3 過濾器作用解析
| 過濾器 | 順序 | 作用 |
|---|---|---|
| DisableEncodeUrlFilter | 1/10 | 防止 URL 編碼問題 |
| WebAsyncManagerIntegrationFilter | 2/10 | 集成 Spring 異步處理 |
| SecurityContextHolderFilter | 3/10 | 從 Session 加載 SecurityContext |
| HeaderWriterFilter | 4/10 | 添加安全相關的 HTTP 頭 |
| CorsFilter | 5/10 | 處理跨域請求 |
| LogoutFilter | 6/10 | 處理用户登出請求 |
| RequestCacheAwareFilter | 7/10 | 恢復之前緩存的請求 |
| SecurityContextHolderAwareRequestFilter | 8/10 | 包裝 Request 對象 |
| ExceptionTranslationFilter | 9/10 | 處理認證和授權異常 |
| AuthorizationFilter | 10/10 | 進行權限檢查 |
六、總結
回到最初的問題:為什麼 Session 中必須放 SecurityContext 而不能直接放 Authentication?
核心答案在於 Spring Security 的過濾器鏈架構:
- 架構要求:
SecurityContext是貫穿整個過濾器鏈的上下文載體 - 完整性:
SecurityContext提供了認證信息的完整生命週期管理 - 擴展性:支持在認證信息之外存儲其他安全相關數據
- 標準化:所有安全過濾器都基於
SecurityContext進行交互
理解了這個架構設計,就能明白為什麼直接存儲 Authentication 會破壞 Spring Security 的完整安全機制,從而導致各種異常和警告。正確的做法是遵循框架設計,使用 SecurityContext 作為認證信息的標準容器。