websocket跨域问题本质是握手阶段被拦截:浏览器在upgrade请求中校验access-control-allow-origin等cors头,服务端必须在101响应前精确返回合法头(如origin匹配、credentials与*互斥),否则连接直接失败。

WebSocket跨域问题不是“能不能连上”的问题,而是“握手阶段就被拦住”的问题——浏览器在发送 Upgrade: websocket 请求时,会检查响应头里有没有合法的 Access-Control-Allow-Origin,没有就直接报错,根本不会走到后续通信。
WebSocket握手阶段必须返回正确的CORS响应头
和普通HTTP请求不同,WebSocket没有预检(OPTIONS)环节,但浏览器仍会严格校验Origin。服务端必须在返回 101 Switching Protocols 前,把跨域头写进响应里。漏掉或写错任何一个关键头,连接就会失败。
-
Access-Control-Allow-Origin必须精确匹配前端域名(如https://app.example.com),不能只写*—— 除非你没用withCredentials: true -
Access-Control-Allow-Credentials: true必须显式设置,且此时Access-Control-Allow-Origin不能为*,否则浏览器直接拒绝 - 如果前端带自定义头(比如
X-Auth-Token),还得加Access-Control-Allow-Headers,否则握手被拒
Django项目中用django-cors-headers配WebSocket
很多人以为装了 django-cors-headers 就自动支持 WebSocket,其实默认只管 HTTP 视图。WebSocket 路由(比如 Django Channels 的 /ws/)需要额外确认中间件是否生效、路由是否被覆盖。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 确保
CorsMiddleware在MIDDLEWARE中排在CommonMiddleware之前,否则可能被跳过 -
CORS_ALLOWED_ORIGINS必须包含ws://和wss://协议前缀的地址(如"ws://localhost:3000"),否则部分浏览器会校验失败 - 如果用的是 Channels 的
AsyncWebsocketConsumer,要确认 ASGI 应用未绕过中间件——某些自定义协议层或健康检查路径可能漏掉 CORS 头
Nginx反向代理时WebSocket跨域最容易踩的坑
用 Nginx 做代理看似一劳永逸,但实际配置中几个关键指令顺序错一位,WebSocket 就会降级成 HTTP 连接,或者握手成功但后续断连。
-
proxy_http_version 1.1和proxy_set_header Upgrade $http_upgrade必须同时存在,缺一不可 -
add_header Access-Control-Allow-Origin要加always标志,否则 Nginx 默认只对 2xx 响应加头,而 101 不算 2xx,头就丢了 - 不要在
location /ws块里写if ($request_method = 'OPTIONS') { ... }—— Nginx 的if在 location 内行为不稳定,容易导致 WebSocket 握手被当成 OPTIONS 拦截
Spring Boot原生WebSocket拦截器必须手动处理Origin
Spring Boot 默认不校验 Origin,也不自动加 CORS 头。如果你没注册 HandshakeInterceptor,浏览器发来的 Origin 头会被忽略,连接直接 403 或静默失败。
- 必须重写
beforeHandshake方法,从ServerHttpRequest取出getHeaders().getOrigin(),再手动往ServerHttpResponse写头 - 不能只依赖
@CrossOrigin注解——它只对 Controller 生效,对 WebSocket 握手无效 - 若启用了 Spring Security,还要确保
/ws/**路径被permitAll()放行,否则安全过滤器会在握手前就返回 403
真正卡住人的从来不是“怎么加头”,而是“哪个环节漏掉了头”:Django 中间件顺序、Nginx 的 always 标志、Spring 的拦截器注册时机——这些地方一旦出错,控制台只显示模糊的 “Error during WebSocket handshake”,没有具体哪一行响应头缺失的提示。










