跳到主要内容
版本:6.4.3

并发支持

QWen Max 中英对照 Java’s Concurrency APIs 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,并确保在之后清除SecurityContextHolder。DelegatingSecurityContextRunnable看起来像这样:

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

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

  • 启动我们创建的线程

由于通常会使用 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);

代码执行以下步骤:

  • 创建将用于 DelegatingSecurityContextExecutor 的 SecurityContext。请注意,在此示例中,我们只是手动创建 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) 时使用 SecurityContextHolder 中的用户(即当前登录的用户)来处理 originalRunnable 呢?这可以通过从我们的 DelegatingSecurityContextExecutor 构造函数中移除 SecurityContext 参数来实现。例如:

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

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

Spring Security 并发类​

请参阅 Javadoc 以获取与 Java 并发 API 和 Spring Task 抽象的其他集成。一旦你理解了前面的代码,它们就相当不言自明了。

  • DelegatingSecurityContextCallable

  • DelegatingSecurityContextExecutor

  • DelegatingSecurityContextExecutorService

  • DelegatingSecurityContextRunnable

  • DelegatingSecurityContextScheduledExecutorService

  • DelegatingSecurityContextSchedulingTaskExecutor

  • DelegatingSecurityContextAsyncTaskExecutor

  • DelegatingSecurityContextTaskExecutor

  • DelegatingSecurityContextTaskScheduler