并发支持
在大多数环境中,Security 是基于每个 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
中获取它)。* 创建一个负责执行提交的 Runnable
对象的 delegateExecutor
。* 最后,我们创建了一个 DelegatingSecurityContextExecutor
,它负责将传递到 execute
方法中的任何 Runnable
包装成一个 DelegatingSecurityContextRunnable
。然后,它将包装后的 Runnable
传递给 delegateExecutor
。在这种情况下,对于提交给我们的 DelegatingSecurityContextExecutor
的每个 Runnable
,都使用相同的 SecurityContext
。如果我们运行需要以具有提升权限的用户身份运行的后台任务,这将非常有用。* 此时,你可能会问自己,“这如何使我的代码对 Spring Security 一无所知?” 我们可以在自己的代码中注入一个已经初始化的 DelegatingSecurityContextExecutor
实例,而不是在代码中创建 SecurityContext
和 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)
时使用 SecurityContextHolder
中的用户(即当前登录的用户)来处理 originalRunnable
该怎么办?你可以通过从 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 任务抽象的其他集成。一旦你理解了前面的代码,它们就很容易理解了。