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
相关产品推荐
相关产品推荐

