并发支持

在大多数环境中,安全信息存储在每个Thread的基础上。这意味着当在新Thread上执行工作时,SecurityContext会丢失。Spring Security 提供了一些基础设施来帮助用户更轻松地处理这种情况。Spring Security 为在多线程环境中使用 Spring Security 提供了低级抽象。事实上,这就是 Spring Security 用于与AsyncContext.start(Runnable)Spring MVC 异步集成集成的基础。

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转移到调用受保护服务的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)

代码执行以下步骤

  • 创建用于我们的DelegatingSecurityContextExecutorSecurityContext。请注意,在此示例中,我们只是手动创建SecurityContext。但是,我们获取SecurityContext的位置或方式并不重要(即,如果需要,我们可以从SecurityContextHolder中获取它)。

  • 创建一个委托执行器,负责执行提交的Runnable

  • 最后,我们创建一个DelegatingSecurityContextExecutor,它负责使用DelegatingSecurityContextRunnable包装传递到execute方法的任何Runnable。然后,它将包装后的Runnable传递给委托执行器。在此实例中,将为提交到我们的DelegatingSecurityContextExecutor的每个Runnable使用相同的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)时,首先由SecurityContextHolder获取SecurityContext,然后使用该SecurityContext创建我们的DelegatingSecurityContextRunnable。这意味着我们正在使用与调用executor.execute(Runnable)代码相同的用户运行我们的Runnable

Spring Security 并发类

有关与Java并发API和Spring Task抽象的更多集成,请参阅Javadoc。一旦您理解了前面的代码,它们就非常容易理解。

  • DelegatingSecurityContextCallable

  • DelegatingSecurityContextExecutor

  • DelegatingSecurityContextExecutorService

  • DelegatingSecurityContextRunnable

  • DelegatingSecurityContextScheduledExecutorService

  • DelegatingSecurityContextSchedulingTaskExecutor

  • DelegatingSecurityContextAsyncTaskExecutor

  • DelegatingSecurityContextTaskExecutor

  • DelegatingSecurityContextTaskScheduler