并发支持
在大多数环境中,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