# 并发支持

在大多数环境中,安全性是以 perThread为基础存储的。这意味着,当在新的Thread上完成工作时,SecurityContext将丢失。 Spring 安全性提供了一些基础设施,以帮助用户更容易地实现这一点。 Spring 安全性为在多线程环境中使用 Spring 安全性提供了低层次的抽象。事实上,这就是 Spring 安全性构建到与AsyncContext.start(可运行)Spring MVC Async Integration集成的基础。

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

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

爪哇

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

Kotlin

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

虽然非常简单,但它可以无缝地将 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();

Kotlin

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

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();

Kotlin

val originalRunnable = Runnable {
    // invoke secured service
}

val wrappedRunnable = DelegatingSecurityContextRunnable(originalRunnable)

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);

Kotlin

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,它负责执行提交的Runnables

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

  • 此时,你可能会问自己:“这是如何屏蔽我的代码中的任何安全知识的?”我们不需要在自己的代码中创建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);
}

Kotlin

@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参数来完成。例如:

爪哇

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

Kotlin

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

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

# Spring 安全并发类

有关 Java Concurrent API 和 Spring 任务抽象的附加集成,请参考 Javadoc。一旦你理解了前面的代码,它们就非常不言自明了。

  • DelegatingSecurityContextCallable

  • DelegatingSecurityContextExecutor

  • DelegatingSecurityContextExecutorService

  • DelegatingSecurityContextRunnable

  • DelegatingSecurityContextScheduledExecutorService

  • DelegatingSecurityContextSchedulingTaskExecutor

  • DelegatingSecurityContextAsyncTaskExecutor

  • DelegatingSecurityContextTaskExecutor

  • DelegatingSecurityContextTaskScheduler