Concurrency Support
在大多数环境中,安全性以 Thread`为基础存储。这意味着当对一个新的 `Thread`进行处理时,`SecurityContext`会丢失。Spring Security 提供了一些基础设施来帮助使管理变得更加容易。Spring Security 为在多线程环境中使用 Spring Security 提供低级抽象。事实上,这是 Spring Security 建立在与 `AsyncContext.start(Runnable)
和 Spring MVC Async Integration集成的基础之上。
DelegatingSecurityContextRunnable
Spring Security 的并发支持中最基本的构建模块之一是 DelegatingSecurityContextRunnable
。它包装了一个委托 Runnable
,并使用指定的 SecurityContext
为委托初始化 SecurityContextHolder
。然后它调用委托 Runnable
,确保之后清除 SecurityContextHolder
。DelegatingSecurityContextRunnable
看起来是这样的:
public void run() {
try {
SecurityContextHolder.setContext(securityContext);
delegate.run();
} finally {
SecurityContextHolder.clearContext();
}
}
虽然非常简单,但它使将 SecurityContext`从一个 `Thread`无缝地传输到另一个 `Thread`变得容易。这很重要,因为在大多数情况下,`SecurityContextHolder`都是基于每个 `Thread`采取行动的。例如,您可能已使用 Spring Security 的
<global-method-security>`支持来保护您的某个服务。现在,您可以将当前 Thread`的 `SecurityContext`传输到调用受保护服务的 `Thread
。以下示例显示了如何执行此操作:
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
SecurityContext context = SecurityContextHolder.getContext();
DelegatingSecurityContextRunnable wrappedRunnable =
new DelegatingSecurityContextRunnable(originalRunnable, context);
new Thread(wrappedRunnable).start();
前面的代码:
-
创建
Runnable
,它将调用我们的安全服务。请注意,它不知道 Spring Security。 -
从
SecurityContextHolder`获得我们希望使用的 `SecurityContext
,并初始化DelegatingSecurityContextRunnable
。 -
使用
DelegatingSecurityContextRunnable`创建 `Thread
。 -
启动我们创建的
Thread
。
由于使用来自 SecurityContextHolder
的 SecurityContext
创建 DelegatingSecurityContextRunnable
很常见,因此它有一个快捷构造函数。以下代码的效果与前面的代码相同:
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
DelegatingSecurityContextRunnable wrappedRunnable =
new DelegatingSecurityContextRunnable(originalRunnable);
new Thread(wrappedRunnable).start();
我们编写的代码使用起来很简单,但它仍然需要知道我们正在使用 Spring Security。在下一节中,我们将了解如何利用 DelegatingSecurityContextExecutor
来隐藏我们正在使用 Spring Security 的事实。
DelegatingSecurityContextExecutor
在前一节中,我们发现 DelegatingSecurityContextRunnable
很容易使用,但并不是最理想的,因为我们必须了解 Spring Security 才能使用它。现在,我们来看看 DelegatingSecurityContextExecutor
如何保护我们的代码,避免了解我们正在使用 Spring Security。
DelegatingSecurityContextExecutor
的设计类似于 DelegatingSecurityContextRunnable
的设计,除了它接受一个委托 Executor
,而不是一个委托 Runnable
。以下示例显示了如何使用它:
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);
此代码:
请注意,在此示例中,我们手工创建了 SecurityContext
。但是,无论我们在何处或如何获取 SecurityContext
(例如,我们可以从 SecurityContextHolder
获取它),这并不重要。* 创建了一个 delegateExecutor
,负责执行提交的 Runnable
对象。* 最后,我们创建了一个 DelegatingSecurityContextExecutor
,负责使用 DelegatingSecurityContextRunnable
封装传递给 execute
方法的任何 Runnable
。然后它将封装的 Runnable
传递给 delegateExecutor
。在本例中,对提交给我们的 DelegatingSecurityContextExecutor
的每个 Runnable
使用相同的 SecurityContext
。如果我们运行需要由具有提升权限的用户运行的后台任务,这会很好。* 此时,您可能会自问,“How does this shield my code of any knowledge of Spring Security?” 相反,我们可以在自己的代码中创建 SecurityContext
和 DelegatingSecurityContextExecutor
,也可以注入一个已初始化的 DelegatingSecurityContextExecutor
实例。
请考虑以下示例:
@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);
}
现在,我们的代码不知道 SecurityContext
正在传播到 Thread
,originalRunnable
正在运行,SecurityContextHolder
已被清除。在此示例中,同一个用户用于运行每个线程。如果我们希望在调用 executor.execute(Runnable)
处理 originalRunnable
时使用 SecurityContextHolder
中的用户(即当前登录用户),该怎么办?您可以通过从 DelegatingSecurityContextExecutor
构造函数中移除 SecurityContext
参数来实现:
SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();
DelegatingSecurityContextExecutor executor =
new DelegatingSecurityContextExecutor(delegateExecutor);
现在,任何时候运行 executor.execute(Runnable)
,SecurityContext
首先由 SecurityContextHolder
获取,然后该 SecurityContext
用于创建我们的 DelegatingSecurityContextRunnable
。这意味着我们使用用于调用 executor.execute(Runnable)
代码的相同用户来运行我们的 Runnable
。
Spring Security Concurrency Classes
查看 {security-api-url}index.html[Javadoc] 以了解与 Java 并发 API 和 Spring Task 抽象的更多集成。一旦理解了之前的代码,它们便不言自明。
-
{security-api-url}org/springframework/security/concurrent/DelegatingSecurityContextCallable.html[
DelegatingSecurityContextCallable
] -
{security-api-url}org/springframework/security/concurrent/DelegatingSecurityContextExecutor.html[
DelegatingSecurityContextExecutor
] -
{security-api-url}org/springframework/security/concurrent/DelegatingSecurityContextExecutorService.html[
DelegatingSecurityContextExecutorService
] -
{security-api-url}org/springframework/security/concurrent/DelegatingSecurityContextRunnable.html[
DelegatingSecurityContextRunnable
] -
{security-api-url}org/springframework/security/concurrent/DelegatingSecurityContextScheduledExecutorService.html[
DelegatingSecurityContextScheduledExecutorService
] -
{security-api-url}org/springframework/security/scheduling/DelegatingSecurityContextSchedulingTaskExecutor.html[
DelegatingSecurityContextSchedulingTaskExecutor
] -
{security-api-url}org/springframework/security/task/DelegatingSecurityContextAsyncTaskExecutor.html[
DelegatingSecurityContextAsyncTaskExecutor
] -
{security-api-url}org/springframework/security/task/DelegatingSecurityContextTaskExecutor.html[
DelegatingSecurityContextTaskExecutor
] -
{security-api-url}org/springframework/security/scheduling/DelegatingSecurityContextTaskScheduler.html[
DelegatingSecurityContextTaskScheduler
]