并发支持
在大多数环境中,安全信息是按 Thread
(线程)存储的。这意味着当在新 Thread
上执行工作时,SecurityContext
会丢失。Spring Security 提供了一些基础设施来帮助用户更容易地处理这个问题。Spring Security 为在多线程环境中与 Spring Security 协作提供了低级抽象。实际上,这是 Spring Security 用于集成 AsyncContext.start(Runnable) 和 Spring MVC 异步集成的基础。
DelegatingSecurityContextRunnable
Spring Security 并发支持中最基础的构建块之一是 DelegatingSecurityContextRunnable
。它包装了一个委托 Runnable
,以便为委托初始化 SecurityContextHolder
,并使用指定的 SecurityContext
。然后它会调用委托 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 成为可能。这很重要,因为在大多数情况下,SecurityContextHolder 是按线程工作的。例如,您可能使用了 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
非常常见,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)
代码执行以下步骤
-
创建用于
DelegatingSecurityContextExecutor
的SecurityContext
。请注意,在这个示例中,我们只是手动创建了SecurityContext
。然而,我们从哪里或如何获取SecurityContext
并不重要(例如,如果需要,我们可以从SecurityContextHolder
获取)。 -
创建一个 delegateExecutor,它负责执行提交的
Runnable
-
最后,我们创建一个
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