WebSocket跨域本质是握手阶段Origin校验,非CORS预检;403错误表明服务端拒绝升级,需Nginx透传Upgrade/Connection头、Spring Boot用HandshakeInterceptor校验Origin,且Origin值必须严格匹配。

WebSocket握手阶段被拦截,不是CORS预检问题
WebSocket跨域和HTTP CORS根本不是一回事:它没有OPTIONS预检,只在HTTP Upgrade握手阶段检查Origin头,并依赖服务端返回HTTP 101 Switching Protocols响应。浏览器控制台报错如Error during WebSocket handshake: Unexpected response code: 403,说明服务端拒绝了握手请求,而不是响应头没加对。
常见误操作包括:
- 给WebSocket服务硬塞
Access-Control-Allow-Origin响应头——这无效,因为握手成功后就不是HTTP了 - 用Nginx转发但没开启
Upgrade和Connection透传 - Spring Cloud Gateway里配了
CorsWebFilter却没关掉只读headers限制,直接抛UnsupportedOperationException
Nginx反向代理WebSocket必须透传Upgrade头
如果用Nginx做入口,只加add_header Access-Control-Allow-Origin *完全没用。关键是要让Upgrade请求原样抵达后端服务。
必须配置以下三项(缺一不可):
-
proxy_http_version 1.1;(启用HTTP/1.1,否则不支持Upgrade) -
proxy_set_header Upgrade $http_upgrade;(把客户端的Upgrade: websocket头传下去) -
proxy_set_header Connection "upgrade";(显式声明连接升级,不能写$http_connection,否则可能被覆盖成close)
漏掉任意一条,Nginx会按普通HTTP处理,返回200或404,导致WebSocket握手失败。
Spring Boot原生WebSocket需自定义HandshakeInterceptor
Spring Boot默认不校验Origin,但生产环境必须校验。不能靠@CrossOrigin注解——它只对HTTP接口生效,对WebSocket握手无效。
将 WCAG 2 AA 无障碍标准应用于设计系统、UI 组件和网页——通过可运行的验证器进行对比度计算,采用设计 token 模式使颜色天生可访问,设定排版和焦点状态规则,并提供语义检查清单。适用于构建或审查 UI。
正确做法是实现HandshakeInterceptor:
- 重写
beforeHandshake方法,从request.getHeaders().getOrigin()取来源 - 白名单比对后,调用
response.getHeaders().set("Sec-WebSocket-Origin", origin)(部分老客户端需要) - 务必返回
true放行,返回false会直接拒绝握手
注意:response.setHeader("Access-Control-Allow-Origin", "*")在这里毫无意义,别加。
Websocat或自建服务要设custom-reply-headers
像Websocat这类轻量级WebSocket服务,没有内置CORS逻辑,必须手动注入响应头。
启动命令中必须带--custom-reply-headers参数:
- 基础可用:
websocat ws-l:0.0.0.0:8080 --custom-reply-headers "Access-Control-Allow-Origin: https://your-app.com" - 若需凭证(如cookie),必须同时加
Access-Control-Allow-Credentials: true,且Origin不能为* - 生产环境禁止用
*通配符,必须精确匹配前端域名,否则withCredentials: true会失效
这个头只在握手响应里起作用,后续帧通信不走HTTP,所以不用再管其他头。
最易被忽略的一点:所有方案都要求前后端Origin值严格一致——https://a.com 和 https://a.com/ 结尾斜杠不同,也算跨域;localhost:3000 和 127.0.0.1:3000 也不同源。别只盯着头,先核对浏览器发出来的Origin到底是什么。

















