并发支持
在大多数环境中,安全信息存储在每个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` 看起来像这样
-
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> 支持来保护您的其中一项服务。现在您可以轻松地将当前 `Thread` 的 `SecurityContext` 转移到调用受保护服务的 `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` 创建一个线程
-
启动我们创建的线程
由于使用 `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)
代码执行以下步骤
-
创建用于我们的 `DelegatingSecurityContextExecutor` 的 `SecurityContext`。请注意,在这个例子中,我们只是手动创建 `SecurityContext`。但是,我们从哪里或如何获取 `SecurityContext` 并不重要(例如,如果需要,我们可以从 `SecurityContextHolder` 中获取它)。
-
创建一个 delegateExecutor,负责执行提交的
Runnable
。 -
最后,我们创建一个
DelegatingSecurityContextExecutor
,它负责用DelegatingSecurityContextRunnable
包装传递给 execute 方法的任何 Runnable。然后,它将包装后的 Runnable 传递给 delegateExecutor。在本例中,将使用相同的SecurityContext
来执行提交到DelegatingSecurityContextExecutor
的每个 Runnable。如果我们正在运行需要由具有提升权限的用户运行的后台任务,这将非常有用。 -
此时,您可能会问自己“这如何屏蔽我的代码对 Spring Security 的任何了解?” 而不是在自己的代码中创建
SecurityContext
和DelegatingSecurityContextExecutor
,我们可以注入一个已初始化的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 并发类
有关与 Java 并发 API 和 Spring 任务抽象的更多集成,请参阅 Javadoc。一旦您理解了前面的代码,它们就非常容易理解。
-
DelegatingSecurityContextCallable
-
DelegatingSecurityContextExecutor
-
DelegatingSecurityContextExecutorService
-
DelegatingSecurityContextRunnable
-
DelegatingSecurityContextScheduledExecutorService
-
DelegatingSecurityContextSchedulingTaskExecutor
-
DelegatingSecurityContextAsyncTaskExecutor
-
DelegatingSecurityContextTaskExecutor
-
DelegatingSecurityContextTaskScheduler