并发支持

本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。

在大多数环境中,Security 是以每个 Thread 为单位进行存储的。这意味着,当在一个新的线程上工作时,SecurityContext 就会丢失。Spring Security 提供了一些基础设施,以帮助用户更容易地处理这个问题。Spring Security 为在多线程环境中使用Spring Security 提供了低层次的抽象。事实上,这正是 Spring Security 与 AsyncContext.start(Runnable)Spring MVC Async 整合的基础。

DelegatingSecurityContextRunnable

Spring Security 的并发支持中最基本的构建模块之一是 DelegatingSecurityContextRunnable。它封装了一个委托的 Runnable,以便用指定的委托的 SecurityContext 来初始化 SecurityContextHolder。然后,它调用委托的 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 转移到调用安全服务(secured service)的 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 是很常见的,因此有一个快捷的构造函数。下面的代码与上面的代码相同。

  • 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,它负责用 DelegatingSecurityContextRunnable 来包装任何被传入执行方法的 Runnable。然后,它将包装好的 Runnable 传递给 delegateExecutor。在这种情况下,每个提交给 DelegatingSecurityContextExecutorRunnable 都将使用相同的 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) 被执行,SecurityContext 首先被 SecurityContextHolder 获得,然后该 SecurityContext 被用于创建我们的 DelegatingSecurityContextRunnable。这意味着我们是用调用 executor.execute(Runnable) 代码的同一个用户来运行我们的 Runnable

Spring Security 的并发类

请参考Javadoc,了解与 Java 并发 API 和 Spring Task 抽象(abstract)的额外集成。一旦你理解了前面的代码,它们就不言自明了。

  • DelegatingSecurityContextCallable

  • DelegatingSecurityContextExecutor

  • DelegatingSecurityContextExecutorService

  • DelegatingSecurityContextRunnable

  • DelegatingSecurityContextScheduledExecutorService

  • DelegatingSecurityContextSchedulingTaskExecutor

  • DelegatingSecurityContextAsyncTaskExecutor

  • DelegatingSecurityContextTaskExecutor

  • DelegatingSecurityContextTaskScheduler