併發支援
在大多數環境中,Security 是按每個 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獲取它)。 -
建立一個負責執行提交的
Runnable的delegateExecutor -
最後,我們建立一個
DelegatingSecurityContextExecutor,它負責將傳遞給 execute 方法的任何 Runnable 包裝在DelegatingSecurityContextRunnable中。然後它將包裝的 Runnable 傳遞給 delegateExecutor。在此例項中,對於提交給我們的DelegatingSecurityContextExecutor的每個 Runnable,將使用相同的SecurityContext。如果我們正在執行需要由具有提升許可權的使用者執行的後臺任務,這將非常有用。 -
此時,您可能會問自己:“這如何使我的程式碼免於瞭解 Spring Security?”與其在我們的程式碼中建立
SecurityContext和DelegatingSecurityContextExecutor,不如注入一個已經初始化過的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 併發類
有關與 Java 併發 API 和 Spring Task 抽象的附加整合,請參閱 Javadoc。一旦您理解了之前的程式碼,它們就非常容易理解了。
-
DelegatingSecurityContextCallable -
DelegatingSecurityContextExecutor -
DelegatingSecurityContextExecutorService -
DelegatingSecurityContextRunnable -
DelegatingSecurityContextScheduledExecutorService -
DelegatingSecurityContextSchedulingTaskExecutor -
DelegatingSecurityContextAsyncTaskExecutor -
DelegatingSecurityContextTaskExecutor -
DelegatingSecurityContextTaskScheduler