并发支持

在大多数环境中,安全信息是按 Thread(线程)存储的。这意味着当在新 Thread 上执行工作时,SecurityContext 会丢失。Spring Security 提供了一些基础设施来帮助用户更容易地处理这个问题。Spring Security 为在多线程环境中与 Spring Security 协作提供了低级抽象。实际上,这是 Spring Security 用于集成 AsyncContext.start(Runnable)Spring MVC 异步集成的基础。

DelegatingSecurityContextRunnable

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

  • Java

  • Kotlin

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

虽然非常简单,但它使得在不同线程之间无缝传输 SecurityContext 成为可能。这很重要,因为在大多数情况下,SecurityContextHolder 是按线程工作的。例如,您可能使用了 Spring Security 的 <global-method-security> 支持来保护您的服务。现在,您可以轻松地将当前 ThreadSecurityContext 传输到调用受保护服务的 Thread。以下是一个示例说明您如何实现这一点

  • Java

  • Kotlin

Runnable originalRunnable = new Runnable() {
public void run() {
	// invoke secured service
}
};

SecurityContext context = SecurityContextHolder.getContext();
DelegatingSecurityContextRunnable wrappedRunnable =
	new DelegatingSecurityContextRunnable(originalRunnable, context);

new Thread(wrappedRunnable).start();
val originalRunnable = Runnable {
    // invoke secured service
}
val context: SecurityContext = SecurityContextHolder.getContext()
val wrappedRunnable = DelegatingSecurityContextRunnable(originalRunnable, context)

Thread(wrappedRunnable).start()

上面的代码执行以下步骤

  • 创建一个 Runnable,它将调用我们的受保护服务。请注意,它不知道 Spring Security

  • SecurityContextHolder 获取我们希望使用的 SecurityContext 并初始化 DelegatingSecurityContextRunnable

  • 使用 DelegatingSecurityContextRunnable 创建一个 Thread

  • 启动我们创建的 Thread

由于从 SecurityContextHolder 获取 SecurityContext 并创建 DelegatingSecurityContextRunnable 非常常见,Spring Security 提供了一个快捷构造函数。以下代码与上面的代码效果相同

  • Java

  • Kotlin

Runnable originalRunnable = new Runnable() {
public void run() {
	// invoke secured service
}
};

DelegatingSecurityContextRunnable wrappedRunnable =
	new DelegatingSecurityContextRunnable(originalRunnable);

new Thread(wrappedRunnable).start();
val originalRunnable = Runnable {
    // invoke secured service
}

val wrappedRunnable = DelegatingSecurityContextRunnable(originalRunnable)

Thread(wrappedRunnable).start()

我们的代码虽然简单易用,但仍然需要知道我们正在使用 Spring Security。在下一节中,我们将看看如何利用 DelegatingSecurityContextExecutor 来隐藏我们使用 Spring Security 的事实。

DelegatingSecurityContextExecutor

在上一节中,我们发现使用 DelegatingSecurityContextRunnable 很简单,但它不够理想,因为我们需要了解 Spring Security 才能使用它。让我们看看 DelegatingSecurityContextExecutor 如何使我们的代码无需了解正在使用 Spring Security。

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

  • Java

  • Kotlin

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);
val context: SecurityContext = SecurityContextHolder.createEmptyContext()
val authentication: Authentication =
    UsernamePasswordAuthenticationToken("user", "doesnotmatter", AuthorityUtils.createAuthorityList("ROLE_USER"))
context.authentication = authentication

val delegateExecutor = SimpleAsyncTaskExecutor()
val executor = DelegatingSecurityContextExecutor(delegateExecutor, context)

val originalRunnable = Runnable {
    // invoke secured service
}

executor.execute(originalRunnable)

代码执行以下步骤

  • 创建用于 DelegatingSecurityContextExecutorSecurityContext。请注意,在这个示例中,我们只是手动创建了 SecurityContext。然而,我们从哪里或如何获取 SecurityContext 并不重要(例如,如果需要,我们可以从 SecurityContextHolder 获取)。

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

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

  • 此时您可能会问自己:“这如何让我的代码无需了解 Spring Security?”。我们不必在自己的代码中创建 SecurityContextDelegatingSecurityContextExecutor,而是可以注入一个已经初始化好的 DelegatingSecurityContextExecutor 实例。

  • Java

  • Kotlin

@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);
}
@Autowired
lateinit var executor: Executor // becomes an instance of our DelegatingSecurityContextExecutor

fun submitRunnable() {
    val originalRunnable = Runnable {
        // invoke secured service
    }
    executor.execute(originalRunnable)
}

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

  • Java

  • Kotlin

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

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

Spring Security 并发类

有关 Java 并发 API 和 Spring Task 抽象的更多集成,请参阅 Javadoc。一旦您理解了前面的代码,它们就非常容易理解。

  • DelegatingSecurityContextCallable

  • DelegatingSecurityContextExecutor

  • DelegatingSecurityContextExecutorService

  • DelegatingSecurityContextRunnable

  • DelegatingSecurityContextScheduledExecutorService

  • DelegatingSecurityContextSchedulingTaskExecutor

  • DelegatingSecurityContextAsyncTaskExecutor

  • DelegatingSecurityContextTaskExecutor

  • DelegatingSecurityContextTaskScheduler