并发支持
在大多数环境中,安全上下文(Security)是按 Thread(线程)进行存储的。
这意味着当在一个新的 Thread 上执行任务时,SecurityContext(安全上下文)会丢失。
Spring Security 提供了一些基础设施,帮助用户更轻松地应对这种情况。
Spring Security 还提供了用于在多线程环境中处理 Spring Security 的底层抽象。
事实上,Spring Security 正是基于这些抽象来实现与 AsyncContext.start(Runnable) 和 Spring MVC 异步集成 的整合。
委托安全上下文可运行对象
Spring Security 并发支持中最基本的构建模块之一是 DelegatingSecurityContextRunnable。
它通过包装一个委托的 Runnable,在执行委托任务前使用指定的 SecurityContextHolder 初始化 SecurityContext。
随后,它调用委托的 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()
}
}
尽管非常简单,但它能够无缝地将 xref page 从一个线程传递到另一个线程。
这一点非常重要,因为在大多数情况下,../../servlet/appendix/namespace/method-security.html#nsa-global-method-security 是以每个线程为基础进行操作的。
例如,你可能已经使用了 Spring Security 的 <global-method-security> 功能来保护你的某个服务。
现在,你可以轻松地将当前线程的 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。 -
从
SecurityContext中获取我们希望使用的SecurityContextHolder,并初始化DelegatingSecurityContextRunnable。 -
使用
DelegatingSecurityContextRunnable来创建一个线程 -
启动我们创建的线程
由于通常会使用来自 DelegatingSecurityContextRunnable 的 SecurityContext 来创建 SecurityContextHolder,因此提供了一个快捷构造函数。
以下代码与上面的代码相同:
-
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 这一事实。
委托安全上下文执行器
在上一节中,我们发现使用 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)
该代码执行以下步骤:
-
创建将用于我们的
SecurityContext的DelegatingSecurityContextExecutor。 请注意,在本示例中,我们只是手动创建了SecurityContext。 然而,我们从何处或以何种方式获取SecurityContext并不重要(例如,如果我们愿意,也可以从SecurityContextHolder中获取)。 -
创建一个委托执行器(delegateExecutor),负责执行已提交的
Runnable任务。 -
最后,我们创建一个
DelegatingSecurityContextExecutor,它负责将传递给 execute 方法的任何 Runnable 用DelegatingSecurityContextRunnable进行包装。 然后,它将包装后的 Runnable 传递给 delegateExecutor。 在此示例中,每次提交到我们的SecurityContext的 Runnable 都会使用相同的DelegatingSecurityContextExecutor。 如果我们需要以具有更高权限的用户身份运行后台任务,这种方式非常有用。 -
此时你可能会问自己:“这如何让我的代码完全无需了解 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。
这意味着我们运行的 Runnable 使用的是与调用 executor.execute(Runnable) 代码时相同的用户。
Spring Security 并发类
有关与 Java 并发 API 和 Spring Task 抽象的更多集成,请参阅 Javadoc。 一旦理解了前面的代码,这些内容就相当容易理解了。
-
DelegatingSecurityContextCallable -
DelegatingSecurityContextExecutor -
DelegatingSecurityContextExecutorService -
DelegatingSecurityContextRunnable -
DelegatingSecurityContextScheduledExecutorService -
DelegatingSecurityContextSchedulingTaskExecutor -
DelegatingSecurityContextAsyncTaskExecutor -
DelegatingSecurityContextTaskExecutor -
DelegatingSecurityContextTaskScheduler