如何用AWS Cognito实现Spring GraphQL订阅的JWT认证?
我用Spring for GraphQL写了一个Java GraphQL服务器,打算通过WebSocket的GraphQL订阅给React客户端推送数据,需要部署到AWS EKS,并用AWS Cognito的JWT做流量认证。想请教怎么实现?能不能配置Spring Security验证Cognito的JWT来支持GraphQL订阅?
我尝试用org.springframework.graphql.server.WebSocketGraphQlInterceptor但没成功,加了Spring Security后所有流量都被拦截,调试时根本进不了这个拦截器。
编辑补充:我已经配置了DefaultSecurityFilterChain和WebSocketGraphQlInterceptor,但拦截器只能捕获mutation,处理不了订阅——触发订阅时一直返回401 Unauthorized,根本不会进入handleConnectionInitialization方法。
我的SecurityFilterChain代码如下:
@Bean public DefaultSecurityFilterChain graphqlSecurityChain(HttpSecurity http) throws Exception { return http.cors(AbstractHttpConfigurer::disable) .csrf(AbstractHttpConfigurer::disable) .sessionManagement( session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests( auth -> auth.requestMatchers(HttpMethod.GET, "/subscription") .permitAll() .anyRequest() .authenticated()) .oauth2ResourceServer( oauthConfigurer -> oauthConfigurer.jwt( jwtConfigurer -> jwtConfigurer.decoder(JwtDecoders.fromIssuerLocation(issuer)))) .build(); }
请问WebSocketGraphQlInterceptor无法正常处理订阅的原因是什么?
核心原因
你的问题根源在于Spring Security的拦截优先级高于GraphQL WebSocket拦截器,且对WebSocket握手请求的认证逻辑处理错误:
- GraphQL订阅的WebSocket握手默认是
GET /graphql请求,但你配置的/subscription路径和实际端点不匹配,导致握手请求被anyRequest().authenticated()拦截。 - 即使路径匹配,你把
/subscription设为permitAll(),会跳过Spring Security的JWT验证,同时Spring Security会先于GraphQL拦截器处理握手请求,没有有效JWT时直接返回401,根本到不了WebSocketGraphQlInterceptor的初始化方法。
正确配置步骤
1. 修正Spring Security配置
确保WebSocket握手请求能通过JWT验证,而非直接放行:
@Bean public DefaultSecurityFilterChain graphqlSecurityChain(HttpSecurity http) throws Exception { return http.cors(AbstractHttpConfigurer::disable) .csrf(AbstractHttpConfigurer::disable) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> // 所有GraphQL请求(含WebSocket握手)需认证 auth.requestMatchers("/graphql").authenticated() .anyRequest().permitAll()) // 非GraphQL请求按需配置 .oauth2ResourceServer(oauthConfigurer -> oauthConfigurer.jwt(jwtConfigurer -> jwtConfigurer.decoder(JwtDecoders.fromIssuerLocation(issuer)))) .build(); }
- 若你的GraphQL WebSocket端点不是默认的
/graphql,替换为实际路径。 - 不要将握手路径设为
permitAll(),否则Spring Security不会验证JWT,后续GraphQL上下文也无法获取认证信息。
2. 配置WebSocketGraphQlInterceptor传递认证上下文
Spring Security完成JWT验证后,会将认证信息存入SecurityContextHolder,此时拦截器可在连接初始化时将信息传递到订阅上下文:
@Component public class AuthWebSocketInterceptor implements WebSocketGraphQlInterceptor { @Override public Mono<Object> handleConnectionInitialization(WebSocketSession session, Map<String, Object> connectionInitPayload) { // 从SecurityContext获取已认证用户信息 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && authentication.isAuthenticated()) { // 将用户信息存入WebSocket会话属性 session.getAttributes().put("user", authentication.getPrincipal()); } return Mono.empty(); // 继续连接初始化流程 } @Override public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, Chain chain) { // 将会话中的用户信息放入GraphQL请求上下文 Authentication authentication = (Authentication) request.getSessionAttributes().get("user"); if (authentication != null) { request.configureExecutionInput((input, builder) -> builder.graphQLContext(context -> context.put("user", authentication)).build()); } return chain.next(request); } }
3. 确认GraphQL WebSocket端点配置
在application.yml中验证端点路径与Security配置一致:
spring: graphql: websocket: path: /graphql
4. EKS部署注意事项
- 确保Ingress控制器支持WebSocket:比如AWS ALB需开启WebSocket代理;NGINX Ingress需添加配置:
proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; - 将Cognito的Issuer URL配置为环境变量,避免硬编码,确保应用能正确初始化JWT解码器。
为什么之前的拦截器不生效?
你的Spring Security配置把/subscription设为permitAll(),但实际WebSocket握手请求是到默认的/graphql路径,导致该请求被anyRequest().authenticated()拦截。若请求头未携带有效JWT,Spring Security直接返回401,完全没机会触发WebSocketGraphQlInterceptor的handleConnectionInitialization方法。
内容的提问来源于stack exchange,提问作者Lucas1983

