如何结合Spring Security实现DGS Subscriptions的认证与授权?
解决DGS框架订阅中connection_init消息的认证问题
核心思路
DGS框架默认不会自动处理WebSocket连接初始化阶段(connection_init)的Authorization信息,需要自定义拦截逻辑提取token并初始化Authentication对象,同时让该对象能在后续订阅方法中被Spring Security识别。
具体实现步骤
1. 自定义WebSocket拦截器提取connection_init中的token
创建实现WebSocketHandlerDecoratorFactory的类,拦截connection_init消息,解析Authorization头(通常为Bearer token),完成认证后将Authentication存入Reactor上下文:
@Component public class SubscriptionAuthInterceptor implements WebSocketHandlerDecoratorFactory { private final AuthenticationManager authenticationManager; public SubscriptionAuthInterceptor(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } @Override public WebSocketHandler decorate(WebSocketHandler handler) { return new WebSocketHandler() { @Override public Mono<Void> handle(WebSocketSession session) { return session.receive() .flatMap(message -> { if (message.getType() == WebSocketMessage.Type.TEXT) { String payload = message.getPayloadAsText(); JsonNode jsonNode = new ObjectMapper().readTree(payload); if ("connection_init".equals(jsonNode.get("type").asText())) { JsonNode authNode = jsonNode.get("payload").get("Authorization"); if (authNode != null) { String token = authNode.asText().replace("Bearer ", ""); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(null, token); return Mono.fromCallable(() -> authenticationManager.authenticate(authRequest)) .flatMap(authentication -> handler.handle(session) .contextWrite(Context.of(Authentication.class, authentication)) ); } } } return handler.handle(session); }); } }; } }
2. 配置SecurityWebFilterChain允许订阅端点的WebSocket握手
无需完全放开/subscriptions,仅允许WebSocket握手请求,后续消息层面由自定义拦截器处理:
@Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { return http .authorizeExchange(exchanges -> exchanges .pathMatchers(HttpMethod.GET, "/subscriptions").permitAll() .anyExchange().authenticated() ) .csrf(ServerHttpSecurity.CsrfSpec::disable) .build(); }
3. 让Spring Security识别Reactor上下文的Authentication
自定义ReactiveAuthenticationManager实现,从Reactor上下文中读取已认证的Authentication:
@Component public class ReactorContextAuthenticationManager implements ReactiveAuthenticationManager { @Override public Mono<Authentication> authenticate(Authentication authentication) { return Mono.deferContextual(contextView -> { if (contextView.hasKey(Authentication.class)) { return Mono.just(contextView.get(Authentication.class)); } return Mono.empty(); }); } }
4. 在订阅方法上使用@PreAuthorize
此时@PreAuthorize注解可正确识别Authentication对象:
@DgsSubscription @PreAuthorize("hasAuthority('read')") public Flux<Entity> subscribeToEntity(@Input String id) { // 业务订阅逻辑 }
补充说明
- 若使用JWT,需替换AuthenticationManager为JwtAuthenticationManager,适配token解析逻辑
- 确保前端传递的connection_init payload格式正确,Authorization字段符合约定
- 该方案支持从connection_init消息中提取认证信息,无需依赖握手阶段的请求头
内容的提问来源于stack exchange,提问作者Crystark
相关产品推荐
相关产品推荐

