36. Concurrency Support
在大多数环境中,安全性是按Thread
进行存储的。这意味着当在新的Thread
上完成工作时,SecurityContext
丢失了。 Spring Security 提供了一些基础架构来帮助用户轻松实现这一点。 Spring Security 提供了用于在多线程环境中使用 Spring Security 的底层抽象。实际上,这就是 Spring Security 与第 15.2.4 节“ AsyncContext.start(Runnable)”和第 37.4 节“ Spring MVC 异步集成”集成的基础。
36.1 DelegatingSecurityContextRunnable
Spring Security 并发支持中最基本的构建块之一是DelegatingSecurityContextRunnable
。它包装了一个委托Runnable
,以便为该委托使用指定的SecurityContext
初始化SecurityContextHolder
。然后,它将调用委托 Runnable,以确保随后清除SecurityContextHolder
。 DelegatingSecurityContextRunnable
看起来像这样:
public void run() {
try {
SecurityContextHolder.setContext(securityContext);
delegate.run();
} finally {
SecurityContextHolder.clearContext();
}
}
尽管非常简单,但可以无缝地将 SecurityContext 从一个线程传输到另一个线程。这很重要,因为在大多数情况下,SecurityContextHolder 会基于每个线程进行操作。例如,您可能已经使用 Spring Security 的第 41.4.1 节“<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
创建线程 -
启动我们创建的线程
由于用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 的事实。
36.2 DelegatingSecurityContextExecutor
在上一节中,我们发现使用DelegatingSecurityContextRunnable
很容易,但是它并不理想,因为我们必须知道 Spring Security 才能使用它。让我们看一下DelegatingSecurityContextExecutor
如何使我们的代码不受使用 Spring Security 的任何知识的影响。
DelegatingSecurityContextExecutor
的设计与DelegatingSecurityContextRunnable
的设计非常相似,不同之处在于它接受委托Executor
而不是委托Runnable
。您可以在下面查看如何使用它的示例:
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
new UsernamePasswordAuthenticationToken("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);
该代码执行以下步骤:
-
创建用于
DelegatingSecurityContextExecutor
的SecurityContext
。请注意,在此示例中,我们仅手动创建了SecurityContext
。但是,在何处或如何获取SecurityContext
无关紧要(也就是说,如果需要,我们可以从SecurityContextHolder
获取它)。 -
创建一个负责执行提交的
Runnable
s 的委托 Actuator -
最后,我们创建一个
DelegatingSecurityContextExecutor
,它负责使用DelegatingSecurityContextRunnable
来包装传递给 execute 方法的所有 Runnable。然后,它将包装的 Runnable 传递给委托 Actuator。在这种情况下,提交给我们DelegatingSecurityContextExecutor
的每个 Runnable 将使用相同的SecurityContext
。如果我们正在运行后台任务,而这些任务需要由特权较高的用户运行,那么这很好。 -
此时,您可能会问自己:“这如何屏蔽我的代码,使其不了解 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)
时使用SecurityContextHolder
中的用户(即当前登录的用户)来处理originalRunnable
怎么办?这可以通过从DelegatingSecurityContextExecutor
构造函数中删除SecurityContext
参数来完成。例如:
SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();
DelegatingSecurityContextExecutor executor =
new DelegatingSecurityContextExecutor(delegateExecutor);
现在,无论何时执行executor.execute(Runnable)
,SecurityContextHolder
都会首先获得SecurityContext
,然后使用SecurityContext
来创建DelegatingSecurityContextRunnable
。这意味着我们将使用与调用executor.execute(Runnable)
代码相同的用户来执行Runnable
。
36.3 Spring 安全性并发类
有关与 Java 并发 API 和 Spring Task 抽象的其他集成,请参考 Javadoc。一旦理解了先前的代码,它们就非常不言自明。
-
DelegatingSecurityContextCallable
-
DelegatingSecurityContextExecutor
-
DelegatingSecurityContextExecutorService
-
DelegatingSecurityContextRunnable
-
DelegatingSecurityContextScheduledExecutorService
-
DelegatingSecurityContextSchedulingTaskExecutor
-
DelegatingSecurityContextAsyncTaskExecutor
-
DelegatingSecurityContextTaskExecutor