You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用DGS与Spring Security实现GraphQL订阅授权

问题原因分析
  1. SecurityContext 无法跨线程传递:你使用了NullSecurityContextRepository,它不会持久化SecurityContext,在WebSocket握手的HTTP请求结束后,SecurityContextPersistenceFilter会直接清空SecurityContextHolder。而GraphQL订阅基于WebSocket,握手完成后,后续的事件推送是在新线程执行的,默认的ThreadLocal无法将初始认证信息传递到这些新线程,导致方法安全校验时SecurityContext为空。
  2. Query/Mutation与订阅的差异:Query和Mutation是单次HTTP请求,整个处理流程在同一个线程内完成,认证信息能保留到方法校验阶段;但订阅是长连接,后续事件处理线程无法继承初始握手时的SecurityContext。
解决方案

方案1:用DelegatingSecurityContextPublisher包装订阅Publisher

直接在数据获取器中,将返回的Publisher用DelegatingSecurityContextPublisher包装,确保每个订阅事件都在初始的SecurityContext环境下执行:

@DgsComponent
@RequiredArgsConstructor
public class StocksDataFetcher implements SupportsGlobalObjectIdentification {
    private final StocksService service;

    @DgsSubscription
    @Secured("ROLE_myRole")
    public Publisher<Stock> stocks(DataFetchingEnvironment dfe) {
        SecurityContext currentContext = SecurityContextHolder.getContext();
        // 包装Publisher,传递当前SecurityContext
        return new DelegatingSecurityContextPublisher<>(service.getStocksPublisher(), currentContext);
    }
}

方案2:切换到可继承的SecurityContextHolder策略

配置Spring Security使用InheritableThreadLocalSecurityContextHolderStrategy,让子线程自动继承父线程的SecurityContext:

@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig {
    // 配置可继承的SecurityContextHolder策略
    @Bean
    public SecurityContextHolderStrategy securityContextHolderStrategy() {
        return new InheritableThreadLocalSecurityContextHolderStrategy();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        final var resolver = new DefaultBearerTokenResolver();
        resolver.setAllowUriQueryParameter(true);

        http.cors()
            .and()
            .authorizeHttpRequests().anyRequest().authenticated()
            .and()
            .oauth2ResourceServer()
            .bearerTokenResolver(resolver)
            .jwt();
            
        return http.build();
    }
}

注意:移除原配置中的NullSecurityContextRepository,它会阻止SecurityContext的传递。

方案3:通过WebSocket握手拦截器保存认证信息

通过握手拦截器将SecurityContext存入WebSocket会话,在后续消息处理时恢复:

  1. 创建握手拦截器:
@Component
public class SecurityWebSocketHandshakeInterceptor implements HandshakeInterceptor {
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) {
        SecurityContext context = SecurityContextHolder.getContext();
        if (context.getAuthentication() != null) {
            attributes.put("SPRING_SECURITY_CONTEXT", context);
        }
        return true;
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {}
}
  1. 配置WebSocket消息处理时恢复SecurityContext:
@Configuration
public class DgsWebSocketConfig {
    private final SecurityWebSocketHandshakeInterceptor handshakeInterceptor;

    public DgsWebSocketConfig(SecurityWebSocketHandshakeInterceptor handshakeInterceptor) {
        this.handshakeInterceptor = handshakeInterceptor;
    }

    @Bean
    public WebSocketHandlerDecoratorFactory webSocketHandlerDecoratorFactory() {
        return handler -> new WebSocketHandlerDecorator(handler) {
            @Override
            public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
                SecurityContext context = (SecurityContext) session.getAttributes().get("SPRING_SECURITY_CONTEXT");
                if (context != null) {
                    SecurityContextHolder.setContext(context);
                }
                try {
                    super.handleMessage(session, message);
                } finally {
                    SecurityContextHolder.clearContext();
                }
            }
        };
    }
}

内容的提问来源于stack exchange,提问作者André

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 18:15:52