并发支持

在大多数环境中,Security 是按每个 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 从一个 Thread 无缝转移到另一个 Thread。这很重要,因为在大多数情况下,SecurityContextHolder 是按每个 Thread 运行的。例如,您可能已经使用了 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 创建一个 Thread

  • 启动我们创建的 Thread

由于使用来自 SecurityContextHolderSecurityContext 创建 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 获取它)。

  • 创建一个负责执行提交的 RunnabledelegateExecutor

  • 最后,我们创建一个 DelegatingSecurityContextExecutor,它负责将传递给 execute 方法的任何 Runnable 包装在 DelegatingSecurityContextRunnable 中。然后它将包装的 Runnable 传递给 delegateExecutor。在此实例中,对于提交给我们的 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

© . This site is unofficial and not affiliated with VMware.