併發支援
在大多數環境中,安全資訊是按 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();
}
}
雖然非常簡單,但這使得在 Thread
之間無縫傳輸 SecurityContext
成為可能。這很重要,因為在大多數情況下,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 呢?” 我們不必在自己的程式碼中建立 SecurityContext
和 DelegatingSecurityContextExecutor
,而是可以注入一個已初始化的 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 併發類
有關 Java 併發 API 和 Spring Task 抽象的更多整合資訊,請參閱 Javadoc。一旦你理解了前面的程式碼,它們就很容易理解了。