併發支援

在大多數環境中,安全資訊是按 Thread(執行緒)儲存的。這意味著當在新 Thread 上執行工作時,SecurityContext 會丟失。Spring Security 提供了一些基礎設施來幫助使用者更容易地處理這個問題。Spring Security 為在多執行緒環境中與 Spring Security 協作提供了低階抽象。實際上,這是 Spring Security 用於整合 AsyncContext.start(Runnable)Spring MVC 非同步整合的基礎。

DelegatingSecurityContextRunnable

Spring Security 併發支援中最基礎的構建塊之一是 DelegatingSecurityContextRunnable。它包裝了一個委託 Runnable,以便為委託初始化 SecurityContextHolder,並使用指定的 SecurityContext。然後它會呼叫委託 Runnable,並確保之後清除 SecurityContextHolderDelegatingSecurityContextRunnable 看起來像這樣

  • 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 成為可能。這很重要,因為在大多數情況下,SecurityContextHolder 是按執行緒工作的。例如,您可能使用了 Spring Security 的 <global-method-security> 支援來保護您的服務。現在,您可以輕鬆地將當前 ThreadSecurityContext 傳輸到呼叫受保護服務的 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 非常常見,Spring Security 提供了一個快捷建構函式。以下程式碼與上面的程式碼效果相同

  • 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)

程式碼執行以下步驟

  • 建立用於 DelegatingSecurityContextExecutorSecurityContext。請注意,在這個示例中,我們只是手動建立了 SecurityContext。然而,我們從哪裡或如何獲取 SecurityContext 並不重要(例如,如果需要,我們可以從 SecurityContextHolder 獲取)。

  • 建立一個 delegateExecutor,它負責執行提交的 Runnable

  • 最後,我們建立一個 DelegatingSecurityContextExecutor,它負責將傳遞給 execute 方法的任何 Runnable 包裝在 DelegatingSecurityContextRunnable 中。然後它將包裝後的 Runnable 傳遞給 delegateExecutor。在這種情況下,提交給我們的 DelegatingSecurityContextExecutor 的每個 Runnable 都將使用相同的 SecurityContext。如果我們執行需要由具有提升許可權的使用者執行的後臺任務,這會很有用。

  • 此時您可能會問自己:“這如何讓我的程式碼無需瞭解 Spring Security?”。我們不必在自己的程式碼中建立 SecurityContextDelegatingSecurityContextExecutor,而是可以注入一個已經初始化好的 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