对于最新的稳定版本,请使用 Spring Security 6.4.1! |
并发支持
在大多数环境中,安全性存储在每个Thread
基础。
这意味着,当在新的Thread
这SecurityContext
丢失了。
Spring Security 提供了一些基础结构来帮助使其更易于管理。
Spring Security 为在多线程环境中使用 Spring Security 提供了低级抽象。
事实上,这就是 Spring Security 集成的基础AsyncContext.start(Runnable)
和 Spring MVC 异步集成。
DelegatingSecurityContextRunnable
Spring Security 的并发支持中最基本的构建块之一是DelegatingSecurityContextRunnable
.
它包装一个委托Runnable
要初始化SecurityContextHolder
使用指定的SecurityContext
对于委托。
然后,它会调用委托Runnable
,确保清除SecurityContextHolder
之后。
这DelegatingSecurityContextRunnable
看起来像这样:
public void run() {
try {
SecurityContextHolder.setContext(securityContext);
delegate.run();
} finally {
SecurityContextHolder.clearContext();
}
}
虽然非常简单,但它可以无缝地传输SecurityContext
从 1Thread
到另一个。
这很重要,因为在大多数情况下,SecurityContextHolder
作用于 per-Thread
基础。
例如,您可能已经使用了 Spring Security 的<global-method-security>
support 保护您的一项服务。
您现在可以将SecurityContext
的当前Thread
到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。 -
获取
SecurityContext
我们希望从SecurityContextHolder
并初始化DelegatingSecurityContextRunnable
. -
使用
DelegatingSecurityContextRunnable
要创建一个Thread
. -
启动
Thread
我们创造了。
由于创建DelegatingSecurityContextRunnable
使用SecurityContext
从SecurityContextHolder
,则有一个 shortcut 构造函数。
以下代码与上述代码具有相同的效果:
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
负责执行 SubmittedRunnable
对象。
* 最后,我们创建一个DelegatingSecurityContextExecutor
,它负责包装任何Runnable
的execute
方法替换为DelegatingSecurityContextRunnable
.
然后,它将包装的Runnable
到delegateExecutor
.
在这种情况下,相同的SecurityContext
用于每个Runnable
提交到我们的DelegatingSecurityContextExecutor
.
如果我们运行需要由具有提升权限的用户运行的后台任务,这很好。
* 此时,您可能会问自己,“这如何保护我的代码免受 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
被清除。
在此示例中,使用同一用户运行每个线程。
如果我们想使用SecurityContextHolder
(即当前登录的用户)在我们调用executor.execute(Runnable)
处理originalRunnable
?
您可以通过删除SecurityContext
参数DelegatingSecurityContextExecutor
构造 函数:
SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();
DelegatingSecurityContextExecutor executor =
new DelegatingSecurityContextExecutor(delegateExecutor);
现在,任何时候executor.execute(Runnable)
运行时,SecurityContext
首先由SecurityContextHolder
然后SecurityContext
用于创建我们的DelegatingSecurityContextRunnable
.
这意味着我们正在运行我们的Runnable
替换为用于调用executor.execute(Runnable)
法典。
Spring Security 并发类
有关与 Java 并发 API 和 Spring Task 抽象的其他集成,请参阅 Javadoc。 一旦您理解了前面的代码,它们就不言自明了。