并发支持
在大多数环境中,安全信息存储在每个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
看起来像这样
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
,它负责将传递到execute
方法的任何Runnable
包装在一个DelegatingSecurityContextRunnable
中。然后它将包装后的Runnable
传递给delegateExecutor
。在这种情况下,相同的SecurityContext
用于提交到我们的DelegatingSecurityContextExecutor
的每个Runnable
。如果我们运行需要由具有提升权限的用户运行的后台任务,这很好。* 在这一点上,你可能会问自己,“这如何屏蔽我的代码对 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 并发类
有关与 Java 并发 API 和 Spring 任务抽象的更多集成,请参阅 Javadoc。 了解了前面的代码后,这些集成就很容易理解了。