并发支持
在大多数环境中,安全信息是按 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();
}
}
虽然非常简单,但这使得在 Thread
之间无缝传输 SecurityContext
成为可能。这很重要,因为在大多数情况下,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
获取)。 * 创建一个负责执行提交的 Runnable
对象的 delegateExecutor
。 * 最后,我们创建一个 DelegatingSecurityContextExecutor
,它负责将传递给 execute
方法的任何 Runnable
对象包装在 DelegatingSecurityContextRunnable
中。然后它将包装后的 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
,originalRunnable
被运行,以及 SecurityContextHolder
被清除。在此示例中,使用相同的用户运行每个线程。如果我们想在调用 executor.execute(Runnable)
处理 originalRunnable
时使用来自 SecurityContextHolder
的用户(即当前登录用户)怎么办?你可以通过从 DelegatingSecurityContextExecutor
构造函数中删除 SecurityContext
参数来实现这一点
SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();
DelegatingSecurityContextExecutor executor =
new DelegatingSecurityContextExecutor(delegateExecutor);
现在,每当运行 executor.execute(Runnable)
时,首先会通过 SecurityContextHolder
获取 SecurityContext
,然后使用该 SecurityContext
创建我们的 DelegatingSecurityContextRunnable
。这意味着我们正在使用与调用 executor.execute(Runnable)
代码相同的用户来运行我们的 Runnable
。
Spring Security 并发类
有关 Java 并发 API 和 Spring Task 抽象的更多集成信息,请参阅 Javadoc。一旦你理解了前面的代码,它们就很容易理解了。