並行支援
在大多數環境中,安全性是依據每個 Thread
儲存的。這表示當在新的 Thread
上完成工作時,SecurityContext
會遺失。 Spring Security 提供了一些基礎架構來協助使用者更輕鬆地完成此操作。 Spring Security 提供了用於在多執行緒環境中使用 Spring Security 的底層抽象。 實際上,這是 Spring Security 建構於與 AsyncContext.start(Runnable) 和 Spring MVC 非同步整合 整合的基礎。
DelegatingSecurityContextRunnable
Spring Security 並行支援中最基本的建構區塊之一是 DelegatingSecurityContextRunnable
。 它封裝了一個委派的 Runnable
,以便使用指定的 SecurityContext
為委派初始化 SecurityContextHolder
。 然後它調用委派的 Runnable,確保之後清除 SecurityContextHolder
。 DelegatingSecurityContextRunnable
看起來像這樣
-
Java
-
Kotlin
public void run() {
try {
SecurityContextHolder.setContext(securityContext);
delegate.run();
} finally {
SecurityContextHolder.clearContext();
}
}
fun run() {
try {
SecurityContextHolder.setContext(securityContext)
delegate.run()
} finally {
SecurityContextHolder.clearContext()
}
}
雖然非常簡單,但它可以無縫地將 SecurityContext 從一個 Thread 傳輸到另一個 Thread。 這很重要,因為在大多數情況下,SecurityContextHolder 依據每個 Thread 運作。 例如,您可能已使用 Spring Security 的 <global-method-security> 支援來保護您的其中一項服務。 您現在可以輕鬆地將目前 Thread
的 SecurityContext
傳輸到調用受保護服務的 Thread
。 以下是如何執行此操作的範例
-
Java
-
Kotlin
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
SecurityContext context = SecurityContextHolder.getContext();
DelegatingSecurityContextRunnable wrappedRunnable =
new DelegatingSecurityContextRunnable(originalRunnable, context);
new Thread(wrappedRunnable).start();
val originalRunnable = Runnable {
// invoke secured service
}
val context: SecurityContext = SecurityContextHolder.getContext()
val wrappedRunnable = DelegatingSecurityContextRunnable(originalRunnable, context)
Thread(wrappedRunnable).start()
上面的程式碼執行以下步驟
-
建立一個將調用我們受保護服務的
Runnable
。 請注意,它並未意識到 Spring Security -
從
SecurityContextHolder
取得我們希望使用的SecurityContext
,並初始化DelegatingSecurityContextRunnable
-
使用
DelegatingSecurityContextRunnable
建立 Thread -
啟動我們建立的 Thread
由於使用來自 SecurityContextHolder
的 SecurityContext
建立 DelegatingSecurityContextRunnable
非常常見,因此有一個捷徑建構子可用。 以下程式碼與上面的程式碼相同
-
Java
-
Kotlin
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
DelegatingSecurityContextRunnable wrappedRunnable =
new DelegatingSecurityContextRunnable(originalRunnable);
new Thread(wrappedRunnable).start();
val originalRunnable = Runnable {
// invoke secured service
}
val wrappedRunnable = DelegatingSecurityContextRunnable(originalRunnable)
Thread(wrappedRunnable).start()
我們擁有的程式碼易於使用,但仍然需要知道我們正在使用 Spring Security。 在下一節中,我們將看看如何利用 DelegatingSecurityContextExecutor
來隱藏我們正在使用 Spring Security 的事實。
DelegatingSecurityContextExecutor
在上一節中,我們發現使用 DelegatingSecurityContextRunnable
很簡單,但並不理想,因為我們必須知道 Spring Security 才能使用它。 讓我們看看 DelegatingSecurityContextExecutor
如何保護我們的程式碼免於任何關於我們正在使用 Spring Security 的知識。
DelegatingSecurityContextExecutor
的設計與 DelegatingSecurityContextRunnable
的設計非常相似,只是它接受委派的 Executor
而不是委派的 Runnable
。 您可以在下面看到如何使用它的範例
-
Java
-
Kotlin
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);
val context: SecurityContext = SecurityContextHolder.createEmptyContext()
val authentication: Authentication =
UsernamePasswordAuthenticationToken("user", "doesnotmatter", AuthorityUtils.createAuthorityList("ROLE_USER"))
context.authentication = authentication
val delegateExecutor = SimpleAsyncTaskExecutor()
val executor = DelegatingSecurityContextExecutor(delegateExecutor, context)
val originalRunnable = Runnable {
// invoke secured service
}
executor.execute(originalRunnable)
程式碼執行以下步驟
-
建立要用於我們的
DelegatingSecurityContextExecutor
的SecurityContext
。 請注意,在本範例中,我們只是手動建立SecurityContext
。 但是,我們從哪裡或如何取得SecurityContext
並不重要(即,如果我們願意,我們可以從SecurityContextHolder
取得它)。 -
建立一個 delegateExecutor,它負責執行提交的
Runnable
-
最後,我們建立一個
DelegatingSecurityContextExecutor
,它負責使用DelegatingSecurityContextRunnable
包裝傳遞到 execute 方法的任何 Runnable。 然後,它將包裝的 Runnable 傳遞給 delegateExecutor。 在此實例中,相同的SecurityContext
將用於提交到我們的DelegatingSecurityContextExecutor
的每個 Runnable。 如果我們正在執行需要由具有提升權限的使用者執行的背景工作,這會很好。 -
此時,您可能會問自己「這如何保護我的程式碼免於任何 Spring Security 的知識?」 我們可以在自己的程式碼中注入已初始化的
DelegatingSecurityContextExecutor
實例,而不是建立SecurityContext
和DelegatingSecurityContextExecutor
。
-
Java
-
Kotlin
@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);
}
@Autowired
lateinit var executor: Executor // becomes an instance of our DelegatingSecurityContextExecutor
fun submitRunnable() {
val originalRunnable = Runnable {
// invoke secured service
}
executor.execute(originalRunnable)
}
現在我們的程式碼不知道 SecurityContext
正在傳播到 Thread
,然後執行 originalRunnable
,然後清除 SecurityContextHolder
。 在此範例中,相同的使用者被用於執行每個執行緒。 如果我們想要在我們調用 executor.execute(Runnable)
時使用來自 SecurityContextHolder
的使用者(即,目前登入的使用者)來處理 originalRunnable
怎麼辦? 這可以透過從我們的 DelegatingSecurityContextExecutor
建構子中移除 SecurityContext
引數來完成。 例如
-
Java
-
Kotlin
SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();
DelegatingSecurityContextExecutor executor =
new DelegatingSecurityContextExecutor(delegateExecutor);
val delegateExecutor = SimpleAsyncTaskExecutor()
val executor = DelegatingSecurityContextExecutor(delegateExecutor)
現在,任何時候執行 executor.execute(Runnable)
時,都會先由 SecurityContextHolder
取得 SecurityContext
,然後該 SecurityContext
用於建立我們的 DelegatingSecurityContextRunnable
。 這表示我們正在使用與調用 executor.execute(Runnable)
程式碼的使用者相同的使用者來執行我們的 Runnable
。
Spring Security 並行類別
請參閱 Javadoc 以取得與 Java 並行 API 和 Spring Task 抽象的額外整合。 一旦您了解先前的程式碼,它們就非常容易理解。
-
DelegatingSecurityContextCallable
-
DelegatingSecurityContextExecutor
-
DelegatingSecurityContextExecutorService
-
DelegatingSecurityContextRunnable
-
DelegatingSecurityContextScheduledExecutorService
-
DelegatingSecurityContextSchedulingTaskExecutor
-
DelegatingSecurityContextAsyncTaskExecutor
-
DelegatingSecurityContextTaskExecutor
-
DelegatingSecurityContextTaskScheduler