跳到主要内容

并发支持

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

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

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

  • 启动我们创建的线程

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

代码执行以下步骤:

  • 创建将用于 DelegatingSecurityContextExecutorSecurityContext。请注意,在此示例中,我们只是手动创建 SecurityContext。但是,我们从哪里以及如何获取 SecurityContext 并不重要(即,如果我们愿意,可以从 SecurityContextHolder 中获取)。

  • 创建一个负责执行提交的 Runnable 的 delegateExecutor

  • 最后,我们创建一个 DelegatingSecurityContextExecutor,它负责将传递给 execute 方法的任何 Runnable 用 DelegatingSecurityContextRunnable 包装起来。然后将包装后的 Runnable 传递给 delegateExecutor。在这种情况下,提交给我们的 DelegatingSecurityContextExecutor 的每个 Runnable 都将使用相同的 SecurityContext。如果我们运行需要以具有提升权限的用户身份运行的后台任务,这将非常方便。

  • 此时,您可能会问自己“这如何使我的代码无需了解 Spring Security?” 我们可以在自己的代码中创建 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);
}
java

现在我们的代码并不知道 SecurityContext 被传播到了 Thread,然后运行了 originalRunnable,接着清除了 SecurityContextHolder。在这个例子中,每个线程都使用相同的用户来运行。如果我们希望在调用 executor.execute(Runnable) 时使用 SecurityContextHolder 中的用户(即当前登录的用户)来处理 originalRunnable 呢?这可以通过从我们的 DelegatingSecurityContextExecutor 构造函数中移除 SecurityContext 参数来实现。例如:

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

现在,每当执行 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