並行處理支援
在大多數環境中,安全性是以每個 Thread
為基礎儲存的。這表示當在新的 Thread
上完成工作時,SecurityContext
會遺失。Spring Security 提供了一些基礎架構,以協助您更輕鬆地管理此問題。Spring Security 提供了低階抽象概念,用於在多執行緒環境中使用 Spring Security。事實上,這也是 Spring Security 建構的基礎,以便與 AsyncContext.start(Runnable)
和 Spring MVC 非同步整合 整合。
DelegatingSecurityContextRunnable
DelegatingSecurityContextRunnable
是 Spring Security 並行處理支援中最基本的建構區塊之一。它包裝了一個委派的 Runnable
,以使用指定的 SecurityContext
初始化委派的 SecurityContextHolder
。然後,它會調用委派的 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
取得它)。 * 建立一個 delegateExecutor
,負責執行提交的 Runnable
物件。 * 最後,我們建立一個 DelegatingSecurityContextExecutor
,負責使用 DelegatingSecurityContextRunnable
包裝傳遞到 execute
方法的任何 Runnable
。然後,它將包裝後的 Runnable
傳遞給 delegateExecutor
。在這種情況下,相同的 SecurityContext
用於提交到我們 DelegatingSecurityContextExecutor
的每個 Runnable
。如果我們執行需要由具有提升權限的使用者執行的背景工作,這會很好。 * 此時,您可能會問自己:「這如何使我的程式碼免於知道 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)
時,SecurityContext
會先由 SecurityContextHolder
取得,然後該 SecurityContext
用於建立我們的 DelegatingSecurityContextRunnable
。這表示我們正在使用與調用 executor.execute(Runnable)
程式碼的使用者相同的使用者來執行我們的 Runnable
。
Spring Security 並行處理類別
請參閱 Javadoc,以取得與 Java 並行 API 和 Spring Task 抽象概念的額外整合。一旦您了解先前的程式碼,它們就會變得不言自明。