# 并发支持

在大多数环境中,安全性是以perThread为基础存储的。这意味着,当在新的Thread上完成工作时,SecurityContext将丢失。 Spring 安全性提供了一些基础设施,以帮助用户更容易地实现这一点。 Spring 安全性为在多线程环境中使用 Spring 安全性提供了低层次的抽象。实际上,这是 Spring 安全性构建在[AsyncContext.start(Runnable)]( Servlet-api.html#servletapi-start-runnable)和Spring MVC Async Integration集成之上的。

# 在可撤销的情况下将证券转让

Spring Security的并发支持中最基本的构建块之一是DelegatingSecurityContextRunnable。它包装了一个委托Runnable,以便用指定的SecurityContext为委托初始化SecurityContextHolder。然后,它调用委托Runnable确保在之后清除SecurityContextHolderDelegatingSecurityContextRunnable看起来是这样的:

public void run() {
try {
	SecurityContextHolder.setContext(securityContext);
	delegate.run();
} finally {
	SecurityContextHolder.clearContext();
}
}

虽然非常简单,但它可以无缝地将SecurityContext从一个线程转移到另一个线程。这一点很重要,因为在大多数情况下,SecurityContextholder是以每个线程为基础的。例如,你可能使用了 Spring Security的[<global-method-security>](../acception/namespace/method-security.html#NSA-global-method-security)支持来保护你的某个服务。现在,你可以轻松地将当前ThreadSecurityContext传输到调用安全服务的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 安全性

  • 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 安全性。在下一节中,我们将研究如何利用委派安全环境专家来隐藏我们正在使用 Spring 安全性的事实。

# DelegatingSecurityContextExecutor

在上一节中,我们发现使用DelegatingSecurityContextRunnable很容易,但并不理想,因为我们必须意识到 Spring 安全性才能使用它。让我们来看看DelegatingSecurityContextExecutor如何保护我们的代码不受我们正在使用 Spring 安全性的任何知识的影响。

DelegatingSecurityContextExecutor的设计与DelegatingSecurityContextRunnable的设计非常相似,只是它接受一个委托Executor而不是一个委托Runnable。你可以在下面看到一个如何使用它的示例:

SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
	new UsernamePasswordAuthenticationToken("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);

代码执行以下步骤:

  • 创建用于我们的DelegatingSecurityContextExecutorSecurityContext。注意,在这个示例中,我们只需手工创建SecurityContext。然而,无论我们在哪里或如何获得SecurityContext都不重要(也就是说,如果我们愿意,我们可以从SecurityContextHolder获得它)。

  • 创建一个DelegateExecutor,它负责执行提交的Runnables

  • 最后,我们创建一个DelegatingSecurityContextExecutor,它负责用DelegatingSecurityContextRunnable包装传递到Execute方法中的任何runnable。然后,它将包装好的Runnable传递给DelegateExecutor。在此实例中,对于提交到我们的DelegatingSecurityContextExecutor的每个runnable,将使用相同的SecurityContext。如果我们运行的是需要由具有提升权限的用户运行的后台任务,那么这很好。

  • 此时,你可能会问自己:“这是如何保护我的代码不受安全知识的影响的?”我们不需要在自己的代码中创建SecurityContextDelegatingSecurityContextExecutor,而是可以插入一个已经初始化的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,该怎么办?这可以通过从我们的DelegatingSecurityContextExecutor构造函数中删除SecurityContext参数来完成。例如:

SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();
DelegatingSecurityContextExecutor executor =
	new DelegatingSecurityContextExecutor(delegateExecutor);

现在,每当执行executor.execute(Runnable)时,SecurityContext首先由SecurityContextHolder得到,然后SecurityContext用于创建我们的DelegatingSecurityContextRunnable。这意味着我们运行Runnable的用户与调用executor.execute(Runnable)代码的用户相同。

# Spring 安全并发类

请参考Javadoc,以获取与 Java 并发API和 Spring 任务抽象的附加集成。一旦你理解了前面的代码,它们就非常不言自明了。

  • DelegatingSecurityContextCallable

  • DelegatingSecurityContextExecutor

  • DelegatingSecurityContextExecutorService

  • DelegatingSecurityContextRunnable

  • DelegatingSecurityContextScheduledExecutorService

  • DelegatingSecurityContextSchedulingTaskExecutor

  • DelegatingSecurityContextAsyncTaskExecutor

  • DelegatingSecurityContextTaskExecutor

  • DelegatingSecurityContextTaskScheduler

整合Jackson