並行支援

在大多數環境中,安全性是依據每個 Thread 儲存的。這表示當在新的 Thread 上完成工作時,SecurityContext 會遺失。 Spring Security 提供了一些基礎架構來協助使用者更輕鬆地完成此操作。 Spring Security 提供了用於在多執行緒環境中使用 Spring Security 的底層抽象。 實際上,這是 Spring Security 建構於與 AsyncContext.start(Runnable)Spring MVC 非同步整合 整合的基礎。

DelegatingSecurityContextRunnable

Spring Security 並行支援中最基本的建構區塊之一是 DelegatingSecurityContextRunnable。 它封裝了一個委派的 Runnable,以便使用指定的 SecurityContext 為委派初始化 SecurityContextHolder。 然後它調用委派的 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 從一個 Thread 傳輸到另一個 Thread。 這很重要,因為在大多數情況下,SecurityContextHolder 依據每個 Thread 運作。 例如,您可能已使用 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

由於使用來自 SecurityContextHolderSecurityContext 建立 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)

程式碼執行以下步驟

  • 建立要用於我們的 DelegatingSecurityContextExecutorSecurityContext。 請注意,在本範例中,我們只是手動建立 SecurityContext。 但是,我們從哪裡或如何取得 SecurityContext 並不重要(即,如果我們願意,我們可以從 SecurityContextHolder 取得它)。

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

  • 最後,我們建立一個 DelegatingSecurityContextExecutor,它負責使用 DelegatingSecurityContextRunnable 包裝傳遞到 execute 方法的任何 Runnable。 然後,它將包裝的 Runnable 傳遞給 delegateExecutor。 在此實例中,相同的 SecurityContext 將用於提交到我們的 DelegatingSecurityContextExecutor 的每個 Runnable。 如果我們正在執行需要由具有提升權限的使用者執行的背景工作,這會很好。

  • 此時,您可能會問自己「這如何保護我的程式碼免於任何 Spring Security 的知識?」 我們可以在自己的程式碼中注入已初始化的 DelegatingSecurityContextExecutor 實例,而不是建立 SecurityContextDelegatingSecurityContextExecutor

  • 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