# 并发支持
在大多数环境中,安全性是以perThread
为基础存储的。这意味着,当在新的Thread
上完成工作时,SecurityContext
将丢失。 Spring 安全性提供了一些基础设施,以帮助用户更容易地实现这一点。 Spring 安全性为在多线程环境中使用 Spring 安全性提供了低层次的抽象。实际上,这是 Spring 安全性构建在[AsyncContext.start(Runnable)
]( Servlet-api.html#servletapi-start-runnable)和Spring MVC Async Integration集成之上的。
# 在可撤销的情况下将证券转让
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的[<global-method-security>
](../acception/namespace/method-security.html#NSA-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 安全性从
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 安全性。在下一节中,我们将研究如何利用委派安全环境专家
来隐藏我们正在使用 Spring 安全性的事实。
# DelegatingSecurityContextExecutor
在上一节中,我们发现使用DelegatingSecurityContextRunnable
很容易,但并不理想,因为我们必须意识到 Spring 安全性才能使用它。让我们来看看DelegatingSecurityContextExecutor
如何保护我们的代码不受我们正在使用 Spring 安全性的任何知识的影响。
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
获得它)。创建一个DelegateExecutor,它负责执行提交的
Runnable
s最后,我们创建一个
DelegatingSecurityContextExecutor
,它负责用DelegatingSecurityContextRunnable
包装传递到Execute方法中的任何runnable。然后,它将包装好的Runnable传递给DelegateExecutor。在此实例中,对于提交到我们的DelegatingSecurityContextExecutor
的每个runnable,将使用相同的SecurityContext
。如果我们运行的是需要由具有提升权限的用户运行的后台任务,那么这很好。此时,你可能会问自己:“这是如何保护我的代码不受安全知识的影响的?”我们不需要在自己的代码中创建
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
。在本例中,使用相同的用户运行每个线程。如果我们希望在调用SecurityContextHolder
时使用来自executor.execute(Runnable)
的用户(即当前登录的用户)来处理originalRunnable
,该怎么办?这可以通过从我们的DelegatingSecurityContextExecutor
构造函数中删除SecurityContext
参数来完成。例如:
SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();
DelegatingSecurityContextExecutor executor =
new DelegatingSecurityContextExecutor(delegateExecutor);
现在,每当执行executor.execute(Runnable)
时,SecurityContext
首先由SecurityContextHolder
得到,然后SecurityContext
用于创建我们的DelegatingSecurityContextRunnable
。这意味着我们运行Runnable
的用户与调用executor.execute(Runnable)
代码的用户相同。
# Spring 安全并发类
请参考Javadoc,以获取与 Java 并发API和 Spring 任务抽象的附加集成。一旦你理解了前面的代码,它们就非常不言自明了。
DelegatingSecurityContextCallable
DelegatingSecurityContextExecutor
DelegatingSecurityContextExecutorService
DelegatingSecurityContextRunnable
DelegatingSecurityContextScheduledExecutorService
DelegatingSecurityContextSchedulingTaskExecutor
DelegatingSecurityContextAsyncTaskExecutor
DelegatingSecurityContextTaskExecutor
DelegatingSecurityContextTaskScheduler