在做一些耗時的操作邏輯時,經常會通過開啓新線程進行處理。但是,直接通過new Thread()/new Runnable()
方式創建的線程中沒有用戶的認證信息(即SecurityContextHolder.getContext().getAuthentication() 返回的是 null)。這是由於 認證信息 是在請求到達後臺,經由 Security 的一系列過濾器後將 認證信息寫入到 線程本地變量中。所以,新開的線程並沒有執行過濾器的邏輯,自然沒有認證信息。
既然不會自動設置認證信息,那我們只有手動設置認證信息了。
方式一: 手動設置線程中的認證信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// 新線程
Runnable runnable = new Runnable() {
public void run() {
// 手動設置線程中的認證信息
SecurityContextHolder.getContext().setAuthentication(authentication);
// 線程處理邏輯(後續就能獲取到認證信息)
...
}
};
new Thread(runnable).start();
方式二:使用 DelegatingSecurityContextRunnable 創建線程
Spring Security 考慮到了新線程需要訪問認證信息的情況,提供了 DelegatingSecurityContextRunnable 類,通過該類構建新線程(Runnable),線程內部自然能獲取認證信息。
查看DelegatingSecurityContextRunnable的源碼, 你會發現,本質上思路和方式一是一致的。
Runnable runnable = new DelegatingSecurityContextRunnable(() -> {
// 線程處理邏輯
...
});
new Thread(runnable).start();
或者
Runnable runnable = DelegatingSecurityContextRunnable.create(() -> {
//線程處理邏輯
...
}, SecurityContextHolder.getContext());
new Thread(runnable).start();
另:
Spring Security 提供一個線程池DelegatingSecurityContextExecutor,線程池類所有線程公用一個認證信息。如果是執行比較通用的邏輯,並不涉及個人的認證信息。其應該是一個比較好的方案。
reference:
官方文檔:Spring Security 對併發的支持
end