跳到主要内容

并发支持

QWen Max 中英对照 Concurrency Concurrency Support

在大多数环境中,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,并在之后确保清除SecurityContextHolderDelegatingSecurityContextRunnable看起来像这样:

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

虽然非常简单,但它使得将 SecurityContext 从一个 Thread 转移到另一个 Thread 变得无缝。这很重要,因为在大多数情况下,SecurityContextHolder 是基于每个 Thread 进行操作的。例如,您可能已经使用了 Spring Security 的 <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();
java

前面的代码:

  • 创建一个 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();
java

我们现有的代码虽然易于使用,但仍然需要知道我们在使用 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);
java

这段代码:

请注意,在此示例中,我们手动创建了 SecurityContext。但是,我们从哪里以及如何获取 SecurityContext 并不重要(例如,我们可以从 SecurityContextHolder 中获取它)。* 创建一个负责执行提交的 Runnable 对象的 delegateExecutor。* 最后,我们创建了一个 DelegatingSecurityContextExecutor,它负责将传递到 execute 方法中的任何 Runnable 包装成一个 DelegatingSecurityContextRunnable。然后,它将包装后的 Runnable 传递给 delegateExecutor。在这种情况下,对于提交给我们的 DelegatingSecurityContextExecutor 的每个 Runnable,都使用相同的 SecurityContext。如果我们运行需要以具有提升权限的用户身份运行的后台任务,这将非常有用。* 此时,你可能会问自己,“这如何使我的代码对 Spring Security 一无所知?” 我们可以在自己的代码中注入一个已经初始化的 DelegatingSecurityContextExecutor 实例,而不是在代码中创建 SecurityContextDelegatingSecurityContextExecutor

请考虑以下示例:

@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);
}
java

现在我们的代码并不知道 SecurityContext 正在被传播到 ThreadoriginalRunnable 被运行,并且 SecurityContextHolder 被清空。在这个例子中,每个线程都在使用相同的用户。如果我们想在调用 executor.execute(Runnable) 时使用 SecurityContextHolder 中的用户(即当前登录的用户)来处理 originalRunnable 该怎么办?你可以通过从 DelegatingSecurityContextExecutor 构造函数中移除 SecurityContext 参数来实现这一点:

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

现在,每当运行 executor.execute(Runnable) 时,SecurityContext 首先由 SecurityContextHolder 获取,然后该 SecurityContext 用于创建我们的 DelegatingSecurityContextRunnable。这意味着我们正在使用调用 executor.execute(Runnable) 代码时所使用的相同用户来运行我们的 Runnable

Spring Security 并发类

参阅 Javadoc,了解与 Java 并发 API 和 Spring 任务抽象的其他集成。一旦你理解了前面的代码,它们就很容易理解了。