首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Spring Security 4 權限機制源碼深度解析:Authentication 存儲與 ThreadLocal 隔離原理

     編輯:Java 綜合教學

核心疑問

  1. 用戶登錄後,Authentication 存在哪裡?
  2. 100 萬用戶並發,系統如何區分每個請求的權限?
  3. 為何同一瀏覽器不同標籤頁登錄不同賬號,權限不會串?

源碼追蹤路徑

1. 認證成功後的存儲入口

UsernamePasswordAuthenticationFilter → successfulAuthentication() → SecurityContextHolder.getContext().setAuthentication(auth)

2. SecurityContextHolder 策略模式

public class SecurityContextHolder {
    private static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
    private static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
    private static final String MODE_GLOBAL = "MODE_GLOBAL";

    private static SecurityContextHolderStrategy strategy;

    static { initialize(); }

    private static void initialize() {
        String strategyName = System.getProperty("spring.security.strategy", MODE_THREADLOCAL);
        if (MODE_THREADLOCAL.equals(strategyName)) {
            strategy = new ThreadLocalSecurityContextHolderStrategy();
        } else if (MODE_INHERITABLETHREADLOCAL.equals(strategyName)) {
            strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
        } else if (MODE_GLOBAL.equals(strategyName)) {
            strategy = new GlobalSecurityContextHolderStrategy();
        } else {
            throw new IllegalStateException("Unknown strategy: " + strategyName);
        }
    }

    public static void setContext(SecurityContext context) {
        strategy.setContext(context);
    }
    // ... getContext, clearContext 同理委派
}

默認策略:ThreadLocalSecurityContextHolderStrategy —— 每個線程獨立副本。

3. ThreadLocalSecurityContextHolderStrategy 實現

public class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
    private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();

    public void setContext(SecurityContext context) {
        if (context == null) {
            contextHolder.remove();
        } else {
            contextHolder.set(context);
        }
    }

    public SecurityContext getContext() {
        SecurityContext ctx = contextHolder.get();
        if (ctx == null) {
            ctx = createEmptyContext();
            contextHolder.set(ctx);
        }
        return ctx;
    }
    // ...
}

關鍵點:ThreadLocal<SecurityContext> 保證線程隔離。

為什麼能區分 100 萬用戶?

  1. Servlet 容器:每個 HTTP 請求由一個線程處理(Tomcat 默認 200 線程池)。
  2. Filter 鏈:SecurityContextPersistenceFilter 在請求開始時從 Session 讀取 SecurityContext 存入 ThreadLocal,請求結束時回寫 Session。
  3. ThreadLocal 語義:同一線程內 get() 看到同一對象;不同線程互不干擾,各自獨立副本。

因此:請求線程 = 權限上下文邊界。用戶 A 的請求走線程 1,用戶 B 走線程 2,ThreadLocal 天然隔離。

同瀏覽器不同標籤頁為何不串?

  • 標籤頁發起的請求屬於不同 HTTP 請求,Servlet 容器分配不同線程處理。
  • 每個線程各自從 Session 讀取各自的 Authentication 存入各自的 ThreadLocal。
  • Session 由 JSESSIONID Cookie 標識,同瀏覽器共享 Cookie,但線程隔離保證運行時不串。

關鍵濾器鏈時序圖

請求進入
   ↓
SecurityContextPersistenceFilter.doFilter()
   ├─ 從 HttpSession 讀取 SecurityContext → ThreadLocal.set()
   ↓
其他業務 Filter / Controller
   ├─ SecurityContextHolder.getContext().getAuthentication()  ← 讀 ThreadLocal
   ↓
SecurityContextPersistenceFilter.doFilter() (finally 塊)
   ├─ ThreadLocal.get() → 寫回 HttpSession
   ├─ ThreadLocal.remove()  防止線程池復用洩漏
   ↓
響應返回

ThreadLocal 核心原理補充

  • Thread 類持有 ThreadLocalMap threadLocals。
  • ThreadLocal.set(value) 實際調用 map.set(this, value),Key 是 ThreadLocal 實例本身。
  • 內存洩漏風險:Key 為弱引用,Value 強引用。線程池長生命周期 + 不調用 remove() → Value 永不回收。Spring Security 在 finally 塊強制 remove() 正是為此。

總結

  1. Authentication 運行時存放在 ThreadLocal<SecurityContext> 中。
  2. SecurityContextPersistenceFilter 負責「Session ⇄ ThreadLocal」雙向同步。
  3. 線程隔離 是多用戶並發權限不串的根本保障,非 存放在全局 Map 或 Client Cookie 中。
  4. 理解 ThreadLocal 是掌握 Spring 事務、Spring Security、Spring MVC 請求上下文等核心機制的前置知識。

源碼雖多,但核心邏輯清晰:策略模式選擇存儲方式 → 默認 ThreadLocal → Filter 同步 Session → 線程隔離保證並發安全。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved