Spring Cloud Gateway 中 WebSocket 请求不经过 GlobalFilter,需用 Route 级 GatewayFilter(order=-2)在 WebsocketRoutingFilter(order=-1)前校验;校验失败须设 401 并调用 setComplete(),成功后才放行;前端推荐 query 传 token。
spring cloud gateway 默认不支持对 websocket 连接做 globalfilter 拦截校验,因为 websocketroutingfilter 会提前“劫持”请求并直接代理,绕过大部分过滤器链。想在校验通过后再建立 websocket 连接,必须在路由前拦截并验证,否则权限控制形同虚设。
WebSocket 请求不会走普通 GlobalFilter 链
这是最容易踩的坑:你写了 AuthorizeFilter,加了 @Order(-1),但发现 /ws 路径完全不触发——因为 Gateway 对 ws:// 或 wss:// 协议的请求,会在 WebsocketRoutingFilter 中直接短路处理,跳过后续所有 GlobalFilter(包括你自定义的鉴权逻辑)。
-
WebsocketRoutingFilter的getOrder()返回-1,优先级极高,几乎最先执行 - 它只检查
exchange.getRequest().getURI().getScheme()是否为ws或wss,匹配即接管 - 此时
ServerWebExchange尚未进入你写的filter()方法,参数、Header 都没机会读取
必须用 GatewayFilter 替代 GlobalFilter 绑定到具体 Route
全局过滤器对 WebSocket 无效,但局部 GatewayFilter 可以绑定到某条 WebSocket Route 上,在 WebsocketRoutingFilter 执行前介入。关键点是:这个 Filter 必须注册在 Route 级别,且 order 要比 WebsocketRoutingFilter 更小(即数值更负)。
- 在
application.yml中配置 Route 时,显式添加自定义GatewayFilter - Filter 实现类需继承
AbstractGatewayFilterFactory,不能只实现GlobalFilter - order 值建议设为
-2,确保在WebsocketRoutingFilter(order = -1)之前执行 - 校验逻辑仍可读取
exchange.getRequest().getHeaders().getFirst("Authorization")或 query 参数
spring:
cloud:
gateway:
routes:
- id: ws-route
uri: ws://backend-service:8080
predicates:
- Path=/ws/**
filters:
- name: AuthWebSocketFilter
args:
headerName: Authorization
expectedValue: Bearer admin-token
AuthWebSocketFilter 实现要点
这个 Filter 必须在连接升级(Upgrade)完成前终止流程,否则 WebSocket 握手已发起,再返回 401 就晚了。核心是判断失败后立即写响应并 return Mono.empty(),阻止后续过滤器执行。
- 不要调用
chain.filter(exchange)放行,否则会落到WebsocketRoutingFilter并直接转发 - 校验失败时,手动设置
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED) - 必须调用
exchange.getResponse().setComplete(),否则连接挂起、客户端超时 - 若需透传用户信息给后端 WebSocket 服务,可在校验成功后用
exchange.mutate().request(...)添加 Header
public class AuthWebSocketFilter extends AbstractGatewayFilterFactory<AuthWebSocketFilter.Config> {
public static class Config {
String headerName = "Authorization";
String expectedValue;
}
<pre class="brush:php;toolbar:false;">@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
String auth = exchange.getRequest().getHeaders().getFirst(config.headerName);
if (auth == null || !auth.equals(config.expectedValue)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange); // 仅校验通过才放行到 WebsocketRoutingFilter
};
}}
前端连接时必须带认证信息,且不能依赖 Cookie
浏览器原生 WebSocket 构造函数不支持传 Header,所以 Authorization 头无法携带。可行方案只有两种:
- 把 token 放 query 参数里,如
new WebSocket("ws://gateway/ws?token=xxx"),后端从exchange.getRequest().getQueryParams().getFirst("token")读取 - 改用
SockJS+Stomp客户端,在 connect 时传headers,网关需配合解析(但需额外引入spring-boot-starter-websocket和 STOMP 支持) - 绝对不要依赖 Cookie:WebSocket 握手是 HTTP GET,但多数网关(包括 Gateway)默认不转发 Cookie 到后端 WS 服务,且跨域场景下 Cookie 本身不可用
真正落地时,query 方式最轻量也最可控;Header 方式需要客户端和网关两端都做适配,容易遗漏握手阶段的 Header 透传配置。


















