併發支援

在大多數環境中,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,確保在之後清除 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)

該程式碼執行以下步驟

  • 為我們的 DelegatingSecurityContextExecutor 建立要使用的 SecurityContext。請注意,在此示例中,我們只是手動建立 SecurityContext。但是,我們從何處或如何獲取 SecurityContext 並不重要(即,如果需要,我們可以從 SecurityContextHolder 獲取它)。

  • 建立一個負責執行提交的 RunnabledelegateExecutor

  • 最後,我們建立一個 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

© . This site is unofficial and not affiliated with VMware.