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

Spring Boot网关WebSocket连接安全咨询:JWT认证不通过Query参数传递

Spring Boot网关下WebSocket的JWT安全配置(避免Query传令牌)

针对你的场景,这里有几个可行的方案,都是基于WebSocket握手阶段的HTTP请求传递JWT,避免令牌暴露在URL中:

方案1:通过Authorization请求头传递JWT(推荐)

WebSocket握手本质是HTTP GET请求,发起连接时可以把JWT放在Authorization: Bearer <token>头里,然后在网关或WebSocket服务端完成校验。

网关层(Spring Cloud Gateway)配置

添加全局过滤器,拦截WebSocket握手请求,提取并校验JWT:

@Component
public class WebSocketJwtFilter implements GlobalFilter, Ordered {
    private final JwtUtil jwtUtil; // 你的JWT工具类

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getPath().toString();
        // 匹配你的WebSocket端点路径
        if (path.startsWith("/ws/")) {
            String authHeader = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
            if (authHeader == null || !authHeader.startsWith("Bearer ")) {
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
            }
            String token = authHeader.substring(7);
            try {
                // 校验JWT,解析后将用户信息存入请求属性
                Claims claims = jwtUtil.parseToken(token);
                exchange.getAttributes().put("userInfo", claims.getSubject());
            } catch (Exception e) {
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
            }
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return -100; // 确保在其他过滤器之前执行
    }
}

WebSocket服务端校验(若网关未做校验,服务端自行处理)

自定义握手拦截器,从请求头提取token校验:

@Component
public class WebSocketHandshakeInterceptor implements HandshakeInterceptor {
    private final JwtUtil jwtUtil;

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            String token = authHeader.substring(7);
            if (jwtUtil.validateToken(token)) {
                Claims claims = jwtUtil.parseToken(token);
                attributes.put("userId", claims.get("userId"));
                return true;
            }
        }
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        return false;
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {}
}

在WebSocket配置类中注册拦截器:

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    private final WebSocketHandshakeInterceptor handshakeInterceptor;
    private final MyWebSocketHandler webSocketHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(webSocketHandler, "/ws/**")
                .addInterceptors(handshakeInterceptor)
                .setAllowedOrigins("*"); // 根据实际场景配置跨域规则
    }
}

方案2:通过HttpOnly Cookie传递JWT(同域场景适用)

如果前端与WebSocket服务处于同域,可以将JWT存入HttpOnly、Secure属性的Cookie中,握手时浏览器会自动携带Cookie,网关或服务端从Cookie提取token校验。

网关层提取示例

// 在WebSocketJwtFilter中修改token提取逻辑
Cookie jwtCookie = exchange.getRequest().getCookies().getFirst("JWT_TOKEN");
if (jwtCookie == null) {
    exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
    return exchange.getResponse().setComplete();
}
String token = jwtCookie.getValue();
// 后续校验逻辑同方案1

方案3:通过自定义WebSocket协议头(Sec-WebSocket-Protocol)传递

部分场景下Authorization头可能被代理拦截,这时可以利用Sec-WebSocket-Protocol头传递JWT,注意后端需要返回相同的头确认连接:

前端发起连接示例

const socket = new WebSocket('ws://your-gateway/ws/endpoint', ['Bearer ' + token]);

网关/服务端校验示例

// 在握手拦截器中提取并校验
String tokenHeader = request.getHeaders().getFirst("Sec-WebSocket-Protocol");
if (tokenHeader != null && tokenHeader.startsWith("Bearer ")) {
    String token = tokenHeader.substring(7);
    if (jwtUtil.validateToken(token)) {
        // 必须返回相同的头,否则连接会失败
        response.getHeaders().add("Sec-WebSocket-Protocol", tokenHeader);
        Claims claims = jwtUtil.parseToken(token);
        attributes.put("userId", claims.get("userId"));
        return true;
    }
}
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return false;

注意事项

  • 无论采用哪种方案,都要设置合理的JWT过期时间,同时实现WebSocket连接的续期逻辑(比如通过心跳包携带新token更新)
  • 跨域场景下需配置CORS,允许对应的请求头(Authorization、Sec-WebSocket-Protocol等)
  • 生产环境必须启用WSS(WebSocket Secure),避免明文传输令牌

内容的提问来源于stack exchange,提问作者Seifeddine Marzouga

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 09:27:27