并发支持
在大多数环境中,安全信息是基于每个 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 从一个线程无缝传输到另一个线程成为可能。这一点很重要,因为在大多数情况下,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创建一个线程 -
启动我们创建的线程
由于使用 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) 时,SecurityContext 会首先由 SecurityContextHolder 获取,然后该 SecurityContext 被用来创建我们的 DelegatingSecurityContextRunnable。这意味着我们正在使用与调用 executor.execute(Runnable) 代码相同的用户来运行我们的 Runnable。
Spring Security 并发类
有关与Java并发API及Spring任务抽象的其他集成,请参阅Javadoc文档。只要理解了前面的代码,这些内容基本不言自明。
DelegatingSecurityContextCallableDelegatingSecurityContextExecutorDelegatingSecurityContextExecutorServiceDelegatingSecurityContextRunnableDelegatingSecurityContextScheduledExecutorServiceDelegatingSecurityContextSchedulingTaskExecutorDelegatingSecurityContextAsyncTaskExecutorDelegatingSecurityContextTaskExecutorDelegatingSecurityContextTaskScheduler