Concurrency Support
在大多数环境中,Security 按每个 Thread
进行存储。这意味着在对新的 Thread
执行操作后,SecurityContext
会丢失。Spring Security 提供了一些基础设施,帮助用户使此过程更加轻松。Spring Security 提供低级别的抽象,用于在多线程环境中使用 Spring Security。实际上,Spring Security 通过这种方式集成到 AsyncContext.start(Runnable) 和 Spring MVC Async Integration 中。
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`,该`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,该 executor 负责执行提交的
Runnable
-
最后,我们创建一个`DelegatingSecurityContextExecutor`,该`DelegatingSecurityContextExecutor`负责使用`DelegatingSecurityContextRunnable`包装传递给 execute 方法的任何 Runnable。然后,它将包装的 Runnable 传递给 delegateExecutor。在这种情况下,对于提交给我们的`DelegatingSecurityContextExecutor`的每个 Runnable,都将使用相同的`SecurityContext`。如果我们要运行需要由具有提升权限的用户运行的后台任务,这很好。
-
此时,你可能会问自己“这如何使我的代码屏蔽 Spring Security 的任何知识?”我们可以注入已初始化的`DelegatingSecurityContextExecutor`实例,而不是在自己的代码中创建`SecurityContext`和`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
。这意味着我们在运行 Runnable
时使用的是与调用 executor.execute(Runnable)
代码时使用的相同用户。
Spring Security Concurrency Classes
有关对 Java 并发 API 和 Spring Task 抽象的附加集成的信息,请参考 Javadoc。一旦你理解了以前的代码,它们就不言自明了。
-
DelegatingSecurityContextCallable
-
DelegatingSecurityContextExecutor
-
DelegatingSecurityContextExecutorService
-
DelegatingSecurityContextRunnable
-
DelegatingSecurityContextScheduledExecutorService
-
DelegatingSecurityContextSchedulingTaskExecutor
-
DelegatingSecurityContextAsyncTaskExecutor
-
DelegatingSecurityContextTaskExecutor
-
DelegatingSecurityContextTaskScheduler