併發支援
在大多數環境中,安全性是按每個 Thread 儲存的。這意味著當在新 Thread 上完成工作時,SecurityContext 將會丟失。Spring Security 提供了一些基礎設施來幫助更輕鬆地管理這一點。Spring Security 提供了用於在多執行緒環境中處理 Spring Security 的低階抽象。實際上,Spring Security 正是基於此來與 AsyncContext.start(Runnable) 和 Spring MVC 非同步整合 整合的。
DelegatingSecurityContextRunnable
Spring Security 併發支援中最基本的構建塊之一是 DelegatingSecurityContextRunnable。它包裝一個委託 Runnable,用於為委託初始化 SecurityContextHolder,並設定一個指定的 SecurityContext。然後它呼叫委託 Runnable,並確保之後清除 SecurityContextHolder。DelegatingSecurityContextRunnable 看起來大致如下:
public void run() {
try {
SecurityContextHolder.setContext(securityContext);
delegate.run();
} finally {
SecurityContextHolder.clearContext();
}
}
雖然非常簡單,但它使 SecurityContext 從一個 Thread 無縫地轉移到另一個 Thread。這很重要,因為在大多數情況下,SecurityContextHolder 是按每個 Thread 工作的。例如,您可能已經使用 Spring Security 的 <global-method-security> 支援來保護您的服務之一。現在,您可以將當前 Thread 的 SecurityContext 轉移到呼叫受保護服務的 Thread。以下示例展示瞭如何做到這一點:
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
SecurityContext context = SecurityContextHolder.getContext();
DelegatingSecurityContextRunnable wrappedRunnable =
new DelegatingSecurityContextRunnable(originalRunnable, context);
new Thread(wrappedRunnable).start();
上述程式碼:
-
建立一個呼叫我們受保護服務的
Runnable。請注意,它不瞭解 Spring Security。 -
從
SecurityContextHolder獲取我們希望使用的SecurityContext,並初始化DelegatingSecurityContextRunnable。 -
使用
DelegatingSecurityContextRunnable建立一個Thread。 -
啟動我們建立的
Thread。
由於通常使用 SecurityContextHolder 中的 SecurityContext 建立 DelegatingSecurityContextRunnable,因此有一個快捷建構函式。以下程式碼與上述程式碼具有相同的效果:
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
DelegatingSecurityContextRunnable wrappedRunnable =
new DelegatingSecurityContextRunnable(originalRunnable);
new Thread(wrappedRunnable).start();
我們的程式碼使用起來很簡單,但它仍然需要我們知道正在使用 Spring Security。在下一節中,我們將介紹如何利用 DelegatingSecurityContextExecutor 來隱藏我們正在使用 Spring Security 的事實。
DelegatingSecurityContextExecutor
在上一節中,我們發現使用 DelegatingSecurityContextRunnable 很簡單,但由於我們必須瞭解 Spring Security 才能使用它,所以它並不理想。現在我們來看看 DelegatingSecurityContextExecutor 如何使我們的程式碼不受任何 Spring Security 知識的影響。
DelegatingSecurityContextExecutor 的設計類似於 DelegatingSecurityContextRunnable,不同之處在於它接受一個委託 Executor 而不是一個委託 Runnable。以下示例展示瞭如何使用它:
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
UsernamePasswordAuthenticationToken.authenticated("user","doesnotmatter", AuthorityUtils.createAuthorityList("ROLE_USER"));
context.setAuthentication(authentication);
SimpleAsyncTaskExecutor delegateExecutor =
new SimpleAsyncTaskExecutor();
DelegatingSecurityContextExecutor executor =
new DelegatingSecurityContextExecutor(delegateExecutor, context);
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
executor.execute(originalRunnable);
這段程式碼:
請注意,在此示例中,我們手動建立 SecurityContext。然而,我們從何處或如何獲取 SecurityContext 並不重要(例如,我們可以從 SecurityContextHolder 獲取它)。 * 建立一個負責執行提交的 Runnable 物件的 delegateExecutor。 * 最後,我們建立一個 DelegatingSecurityContextExecutor,它負責將傳遞給 execute 方法的任何 Runnable 包裝成 DelegatingSecurityContextRunnable。然後它將包裝後的 Runnable 傳遞給 delegateExecutor。在這種情況下,提交給我們 DelegatingSecurityContextExecutor 的每個 Runnable 都使用相同的 SecurityContext。如果我們執行需要由具有提升許可權的使用者執行的後臺任務,這會很有用。 * 此時,您可能會問自己:“這如何使我的程式碼不受任何 Spring Security 知識的影響?”我們可以在自己的程式碼中注入一個已經初始化的 DelegatingSecurityContextExecutor 例項,而不是建立 SecurityContext 和 DelegatingSecurityContextExecutor。
考慮以下示例
@Autowired
private Executor executor; // becomes an instance of our DelegatingSecurityContextExecutor
public void submitRunnable() {
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
executor.execute(originalRunnable);
}
現在我們的程式碼不知道 SecurityContext 正在傳播到 Thread,originalRunnable 正在執行,並且 SecurityContextHolder 正在被清除。在此示例中,每個執行緒都使用相同的使用者執行。如果我們想在呼叫 executor.execute(Runnable) 處理 originalRunnable 時使用 SecurityContextHolder 中的使用者(即當前登入使用者)怎麼辦?您可以透過從 DelegatingSecurityContextExecutor 建構函式中刪除 SecurityContext 引數來實現:
SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();
DelegatingSecurityContextExecutor executor =
new DelegatingSecurityContextExecutor(delegateExecutor);
現在,每當執行 executor.execute(Runnable) 時,首先會透過 SecurityContextHolder 獲取 SecurityContext,然後使用該 SecurityContext 建立我們的 DelegatingSecurityContextRunnable。這意味著我們正在使用呼叫 executor.execute(Runnable) 程式碼的使用者來執行我們的 Runnable。
Spring Security 併發類
請參閱 Javadoc,瞭解與 Java 併發 API 和 Spring Task 抽象的附加整合。一旦您理解了前面的程式碼,它們就一目瞭然。