博客 / 詳情

返回

淺談Spring Security 過濾器鏈

一、問題背景

基於上一篇文章修復登錄警告:SPRING_SECURITY_CONTEXT 未包含 SecurityContext 的原因與解決方案,我們主要講述了錯在哪裏,如何改正,改正後為何不再報錯。而本篇文章則會重點探討:

為什麼 Session 中必須放 SecurityContext 而不能直接放 Authentication
核心原因在於:Spring Security 的整個認證機制是圍繞過濾器鏈運行的。

理解過濾器鏈,你就能徹底理解本次問題的根源和修復原理。

二、Spring Security 過濾器鏈的核心作用

Spring Security 的認證授權機制本質上是一個過濾器鏈,其核心作用是構建完整的安全防護體系。這個體系通過多個過濾器的協同工作,實現了從請求接收到響應返回的全流程安全控制

graph TD
    A[客户端請求] --> B[DelegatingFilterProxy<br/>Servlet與Spring的橋樑]
    B --> C[FilterChainProxy<br/>Spring Security入口]
    C --> D[SecurityFilterChain<br/>安全過濾器鏈]
    
    D --> E[SecurityContextHolderFilter<br/>安全上下文管理]
    E --> F[CsrfFilter<br/>CSRF防護]
    F --> G[認證過濾器<br/>身份驗證]
    G --> H[授權過濾器<br/>權限檢查]
    H --> I[執行業務邏輯]
    
    J[SecurityContext<br/>貫穿整個認證流程] -.-> E
    J -.-> G
    J -.-> H

2.1 過濾器鏈的核心價值

  • 分層防護:每個過濾器專注於特定的安全領域,如CSRF防護、認證、授權等
  • 責任分離:不同的安全 concerns 由不同的過濾器處理,代碼清晰易維護
  • 可擴展性:可以方便地添加、移除或替換特定的安全過濾器
  • 統一入口:所有請求都經過相同的安全檢查和防護

三、深入理解Spring Security 過濾器鏈架構

3.1 從 Servlet Filter 到 Spring Security

在標準的 Servlet 應用中,過濾器鏈是這樣的:

客户端請求Servlet Filter 1Servlet Filter 2...DispatcherServlet

Spring Security 在此基礎上構建了自己的過濾器體系:

graph TD
    A[客户端請求] --> B[Servlet 容器]
    B --> C[DelegatingFilterProxy]
    C --> D[FilterChainProxy]
    D --> E[SecurityFilterChain 0]
    D --> F[SecurityFilterChain 1]
    D --> G[SecurityFilterChain n]
    
    E --> H[SecurityContextHolderFilter]
    H --> I[認證過濾器]
    I --> J[授權過濾器]
    J --> K[業務處理]
    
    L[多個安全過濾器鏈] -.-> D
    M[按請求路徑匹配] -.-> E

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 是連接各個安全過濾器的"上下文總線"。
**

讓我們通過一個具體的請求流程來理解:

graph TD
    A[請求到達] --> B[SecurityContextHolderFilter]
    B --> C[從Session加載SecurityContext]
    C --> D[UsernamePasswordAuthenticationFilter]
    D --> E[認證成功<br/>設置Authentication到SecurityContext]
    E --> F[AuthorizationFilter]
    F --> G[從SecurityContext<br/>獲取Authentication進行授權]
    G --> H[業務處理]
    H --> I[響應返回]
    I --> J[SecurityContextHolderFilter<br/>保存SecurityContext到Session]

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 登錄請求的完整過濾器鏈執行

image.png

  1. 過濾器鏈匹配
  2. 開始處理 POST /user/login 請求
  3. 按順序執行10個安全過濾器
  4. 過濾器鏈執行完成

5.2.2 認證成功後的 SecurityContext 存儲

image.png

  1. 用户認證成功
  2. SecurityContext 被保存到 Session

5.2.3 後續請求的安全上下文恢復

image.png

  1. 後續請求自動恢復 SecurityContext
  2. 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 的過濾器鏈架構:

  1. 架構要求SecurityContext 是貫穿整個過濾器鏈的上下文載體
  2. 完整性SecurityContext 提供了認證信息的完整生命週期管理
  3. 擴展性:支持在認證信息之外存儲其他安全相關數據
  4. 標準化:所有安全過濾器都基於 SecurityContext 進行交互

理解了這個架構設計,就能明白為什麼直接存儲 Authentication 會破壞 Spring Security 的完整安全機制,從而導致各種異常和警告。正確的做法是遵循框架設計,使用 SecurityContext 作為認證信息的標準容器。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.