# 并发支持

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

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

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>`]（../acception/namespace/method-security.html#NSA-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 安全性

* 从`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);
```

代码执行以下步骤：

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

* 创建一个DelegateExecutor，它负责执行提交的`Runnable`s

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

* 此时，你可能会问自己：“这是如何保护我的代码不受安全知识的影响的？”我们不需要在自己的代码中创建`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`。在本例中，使用相同的用户运行每个线程。如果我们希望在调用`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`

[整合](index.html)[Jackson](jackson.html)

