当我阅读有关虚拟线程及其陷阱的内容时,我发现了这一点:
不要在线程局部变量中缓存昂贵的可重用对象
虚拟线程与平台一样支持线程局部变量 线程可以。有关详细信息,请参阅线程局部变量。通常, 线程局部变量用于关联一些特定于上下文的变量 当前运行代码的信息,例如当前 交易和用户 ID。线程局部变量的这种使用是 与虚拟线程完全合理。但是,请考虑使用 更安全、更高效的范围值。有关更多信息,请参阅范围值 信息。
但我还记得 Spring Security 使用 ThreadLocal 来保存给定请求的 SecurityContext:
默认情况下,SecurityContextHolder 使用 ThreadLocal 来存储这些 详细信息,这意味着 SecurityContext 始终可用 同一线程中的方法,即使 SecurityContext 不是 明确地作为参数传递给这些方法。用一个 如果您注意清除 ThreadLocal,这种方式是相当安全的 处理当前委托人的请求后的线程。春天 Security 的 FilterChainProxy 确保 SecurityContext 始终处于 已清除。
文档:https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html
所以问题是:在 Spring Boot REST 应用程序中使用虚拟线程是否安全,其端点确实需要身份验证和授权,因此具有
SecurityContext
?这被认为是一个陷阱吗?
谢谢!
虽然可以 1) 实现自定义
SecurityContextHolderStrategy
,从 SecurityContext
检索 ScopedValue
并将其保存在那里:
public class ScopedSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
private static final ScopedValue<SecurityContextScopedValueHolder> SECURITY_CONTEXT = ScopedValue.newInstance();
private static class SecurityContextScopedValueHolder {
private SecurityContext securityContext;
public SecurityContext getSecurityContext() {
return securityContext;
}
public void setSecurityContext(SecurityContext securityContext) {
this.securityContext = securityContext;
}
}
@Override
public void clearContext() {
retrieveSecurityContextScopedValueHolder().setSecurityContext(null);
}
@Override
public SecurityContext getContext() {
return retrieveSecurityContextScopedValueHolder().getSecurityContext();
}
@Override
public void setContext(SecurityContext context) {
retrieveSecurityContextScopedValueHolder().setSecurityContext(context);
}
@Override
public SecurityContext createEmptyContext() {
return new SecurityContextImpl();
}
private SecurityContextScopedValueHolder retrieveSecurityContextScopedValueHolder() {
if (SECURITY_CONTEXT.isBound()) {
return SECURITY_CONTEXT.get();
} else {
throw new IllegalStateException("Security Context Scoped Value not bound");
}
}
public static ScopedValue.Carrier getSecuriyContextCarrier() {
return ScopedValue.where(SECURITY_CONTEXT, new SecurityContextScopedValueHolder());
}
}
和 2) 配置 Tomcat 以使用绑定到它的
ScopedValue
启动虚拟线程:
@Component
public class TomcatVirtualThreadExecutorCustomizer
implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
private static class ScopedVirtualThreadExecutor extends VirtualThreadExecutor {
public ScopedVirtualThreadExecutor(String namePrefix) {
super(namePrefix);
}
@Override
public void execute(Runnable command) {
super.execute(() -> ScopedSecurityContextHolderStrategy.getSecuriyContextCarrier().run(command));
}
}
@Override
public void customize(TomcatServletWebServerFactory factory) {
factory.addProtocolHandlerCustomizers((protocolHandler) -> protocolHandler
.setExecutor(new ScopedVirtualThreadExecutor("tomcat-handler-")));
}
}
但是,很容易看出这种方法有很大的尴尬。
首先,该方法与 Web 服务器/servlet 容器的类型紧密绑定,在我们的例子中是 Tomcat。如果可能的话,其他服务器(例如 Undertow 或 Jetty)的解决方案可能会有所不同。
其次,Spring Security 是一个无处不在的东西,
SecurityContext
意味着可以在任何地方使用,而不仅仅是在服务器的工作线程上。例如,可能需要在 cron/调度程序线程或仅在由独立 SecurityContext
管理的线程上设置 Executor
。基于 ScopeValue
的方法将需要将其类似地绑定到此类线程,而使用标准 ThreadLocal
绑定 SecurityContextHolderStrategy
可以设置上下文,而无需任何线程调整。
总而言之,这种技术在创建线程的代码和设置/检索的代码之间引入了一些不太受欢迎的耦合。
SecurityContext
。
从概念的角度来看,我敢说结构化编程和 Spring Security 的概念并不能很好地融合在一起——至少在当前版本中是这样。小型 POC Spring Boot 项目可在
此处获得。
请注意,该示例适用于 Spring Boot 3.2.2,但不能保证其对早期和更高版本的适用性,因为目前 Loom 的情况相当不稳定。