并发支持
本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。 |
在大多数环境中,Security 是绑定在当前线程上的。这意味着,当在一个新的线程上工作时,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
从一个线程转移到另一个线程的过程变得无缝。这一点很重要,因为在大多数情况下,SecurityContextHolder
都是以线程为单位行事的。例如,你可能已经使用Spring Security的 <global-method-security>
支持来保护你的一个服务。现在你可以将当前线程的 SecurityContext
转移到调用安全服务的线程中。下面的例子展示了你如何做到这一点。
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的事实。
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
。如果我们运行的后台任务需要由具有高权限的用户来运行,这就很好。 -
这时,你可能会问自己,"这怎么能屏蔽我的代码对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
,原始的 Runnable
被运行,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 并发类
参见 Javadoc,了解与Java并发API和Spring任务抽象的额外集成。一旦你理解了前面的代码,它们就不言自明了。