并发支持
在大多数环境中,安全上下文存储在每个线程的基础上。这意味着当在新线程上执行工作时,安全上下文会丢失。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();
}
}
虽然非常简单,但它使在不同线程之间传输安全上下文变得非常顺畅。这很重要,因为在大多数情况下,SecurityContextHolder
在每个线程的基础上起作用。例如,您可能已使用 Spring Security 的<global-method-security>
支持来保护您的某个服务。现在,您可以将当前线程的安全上下文传输到调用受保护服务的线程。以下示例展示了如何操作
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 并发类
请参阅Javadoc,了解与Java并发API和Spring Task抽象的更多集成。一旦您理解了前面的代码,这些集成就很容易理解了。