Spring Security 虚拟线程和 ThreadLocal

问题描述 投票:0回答:1

当我阅读有关虚拟线程及其陷阱的内容时,我发现了这一点:

不要在线程局部变量中缓存昂贵的可重用对象

虚拟线程与平台一样支持线程局部变量 线程可以。有关详细信息,请参阅线程局部变量。通常, 线程局部变量用于关联一些特定于上下文的变量 当前运行代码的信息,例如当前 交易和用户 ID。线程局部变量的这种使用是 与虚拟线程完全合理。但是,请考虑使用 更安全、更高效的范围值。有关更多信息,请参阅范围值 信息。

这里:https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html#GUID-68216B85-7B43-423E-91BA-11489B1ACA61

但我还记得 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
?这被认为是一个陷阱吗?

谢谢!

java spring spring-boot spring-security virtual-threads
1个回答
0
投票

虽然可以 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 的情况相当不稳定。


© www.soinside.com 2019 - 2024. All rights reserved.