并发支持

在大多数环境中,安全性是按Thread级别存储的。 这意味着当在新Thread上工作时,SecurityContext会丢失。 Spring Security 提供了一些基础架构,使管理变得更加容易。 Spring Security 提供了在多线程环境中使用 Spring Security 的低级抽象。 事实上,这正是 Spring Security 用于与AsyncContext.start(Runnable)Spring MVC 异步集成进行集成的基础。spring-doc.cadn.net.cn

委托安全上下文可运行对象

Spring Security 并发支持中最基本的构建块之一是 DelegatingSecurityContextRunnable。 它包装一个委托的 Runnable,在执行委托任务前,使用指定的 SecurityContextHolder 初始化 SecurityContext。 随后,它会调用该委托的 Runnable,并在执行完成后确保清除 SecurityContextHolderDelegatingSecurityContextRunnable 的大致实现如下所示:spring-doc.cadn.net.cn

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

虽然非常简单,但它使得在Thread之间无缝传输SecurityContext成为可能。 这一点很重要,因为在大多数情况下,SecurityContextHolder是基于每个Thread进行操作的。 例如,您可能曾使用 Spring Security 的<global-method-security>支持来保护您的某个服务。 现在,您可以将当前ThreadSecurityContext传递给调用该受保护服务的Thread。 以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

Runnable originalRunnable = new Runnable() {
public void run() {
	// invoke secured service
}
};

SecurityContext context = SecurityContextHolder.getContext();
DelegatingSecurityContextRunnable wrappedRunnable =
	new DelegatingSecurityContextRunnable(originalRunnable, context);

new Thread(wrappedRunnable).start();

上述代码:spring-doc.cadn.net.cn

由于通常会使用来自 DelegatingSecurityContextRunnableSecurityContext 来创建一个 SecurityContextHolder,因此提供了一个快捷构造函数。 以下代码与前面的代码具有相同的效果:spring-doc.cadn.net.cn

Runnable originalRunnable = new Runnable() {
public void run() {
	// invoke secured service
}
};

DelegatingSecurityContextRunnable wrappedRunnable =
	new DelegatingSecurityContextRunnable(originalRunnable);

new Thread(wrappedRunnable).start();

我们现有的代码使用起来很简单,但仍需要了解我们正在使用 Spring Security。 在下一节中,我们将探讨如何利用 DelegatingSecurityContextExecutor 来隐藏我们正在使用 Spring Security 这一事实。spring-doc.cadn.net.cn

委托安全上下文执行器

在上一节中,我们发现使用 DelegatingSecurityContextRunnable 很简单,但它并不理想,因为我们需要了解 Spring Security 才能使用它。 现在,我们来看看 DelegatingSecurityContextExecutor 如何使我们的代码完全无需知晓正在使用 Spring Security。spring-doc.cadn.net.cn

DelegatingSecurityContextExecutor 的设计与 DelegatingSecurityContextRunnable 类似,不同之处在于它接受一个委托的 Executor,而不是委托的 Runnable。 以下示例展示了如何使用它:spring-doc.cadn.net.cn

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

这段代码:spring-doc.cadn.net.cn

请注意,在此示例中,我们手动创建了 SecurityContext。 然而,我们获取 SecurityContext 的位置或方式并不重要(例如,我们可以从 SecurityContextHolder 中获取它)。 * 创建一个负责执行已提交 Runnable 对象的 delegateExecutor。 * 最后,我们创建一个 DelegatingSecurityContextExecutor,它负责将传入 execute 方法的任何 RunnableDelegatingSecurityContextRunnable 进行包装。 随后,它将包装后的 Runnable 传递给 delegateExecutor。 在这种情况下,每个提交到我们 DelegatingSecurityContextExecutorRunnable 都使用相同的 SecurityContext。 如果我们运行需要由具有提升权限的用户执行的后台任务,这将非常有用。 * 此时,您可能会问自己:“这如何使我的代码对 Spring Security 一无所知?”与其在我们的代码中创建 SecurityContextDelegatingSecurityContextExecutor,不如注入一个已经初始化好的 DelegatingSecurityContextExecutor 实例。spring-doc.cadn.net.cn

考虑以下示例:spring-doc.cadn.net.cn

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

现在我们的代码并不知道 SecurityContext 被传播到了 ThreadoriginalRunnable 被执行了,而 SecurityContextHolder 已被清除。 在此示例中,每个线程都使用同一个用户来运行。 如果我们希望在调用 executor.execute(Runnable) 处理 originalRunnable 时,使用来自 SecurityContextHolder(即当前登录用户)的用户,该怎么办? 您可以通过从 DelegatingSecurityContextExecutor 构造函数中移除 SecurityContext 参数来实现这一点:spring-doc.cadn.net.cn

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

现在,每当执行 executor.execute(Runnable) 时,都会首先通过 SecurityContext 获取 SecurityContextHolder,然后使用该 SecurityContext 创建我们的 DelegatingSecurityContextRunnable。 这意味着我们运行 Runnable 时所使用的用户,与调用 executor.execute(Runnable) 代码的用户是相同的。spring-doc.cadn.net.cn