Origin校验是WebSocket连接前必须执行的强制安全检查,Nginx不能替代后端校验,Gorilla需自定义CheckOrigin函数,生产环境仅允许可信HTTPS白名单,子协议校验和首帧身份绑定缺一不可。

Origin校验不是可选配置,而是WebSocket连接建立前必须执行的强制安全检查;不校验就等于把用户会话钥匙直接交给任意网页。
Origin头为什么不能由Nginx校验
浏览器自动注入的Origin头必须原样透传给后端,Nginx只负责转发,不能做判断或修改:
- 写
proxy_set_header Origin ""会清空原始值,后端收不到req.headers.origin,校验逻辑直接失效 - 用
if ($http_origin ~ ...)在Nginx里拦截,既无法精确比对协议+端口,又容易被构造请求绕过(比如加个空格或换行) -
Sec-WebSocket-Origin是已废弃字段,现代浏览器只发标准Origin,校验它等于没校验 - Nginx没有会话上下文,无法关联
Cookie或验证用户登录态,仅靠域名白名单远远不够
Gorilla WebSocket的CheckOrigin必须自定义
默认Upgrader.CheckOrigin会严格比对Origin和Host,这在开发阶段(如file://、localhost:3000)必然失败,但生产环境又绝不能设为func(r *http.Request) bool { return true }:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 本地调试时可豁免
localhost和127.0.0.1带端口的请求,但需显式判断r.Header.Get("Origin") == ""或协议为http - 生产环境只接受明确的HTTPS白名单,例如
https://app.example.com、https://staging.app.example.com,必须含协议和完整主机名 - 不要用
strings.Contains模糊匹配——https://evil-app.example.com会被example.com误放行 - 校验函数里别做耗时操作(如查DB),握手阶段延迟超过几毫秒就可能触发前端超时
Node.js ws库中Origin校验的典型错误写法
很多项目把Origin校验写在connection事件里,这是错的——连接已建立,攻击者消息可能已经发出去了:
- 必须在
upgrade事件中处理,此时HTTP请求还没升级为WebSocket,还能安全socket.destroy() - 别只检查
req.headers.origin是否存在,空值或null也要拒绝(恶意脚本可伪造无Origin请求) - 别忽略
req.headers.origin里的端口号,https://app.example.com:3001和https://app.example.com应视为不同源 - 校验通过后,立即从
req中提取session cookie并验证有效性,避免“合法来源+非法用户”的组合
子协议(Sec-WebSocket-Protocol)不是摆设
虽然Sec-WebSocket-Protocol不能替代Origin或身份校验,但它能快速过滤掉非正规客户端:
- 前端必须显式声明:
new WebSocket('wss://api.example.com/ws', ['myapp-v1']) - 服务端用
req.headers['sec-websocket-protocol']取值,按逗号分割后trim()再比对,防止空格绕过 - 不匹配就
socket.destroy(),别等连接建立后再返回错误帧——那已经晚了 - 协议名别用泛化词(如
json、v1),应绑定业务域(如chat-v2、notify-internal)
最常被忽略的一点:Origin校验只是第一关,它只回答“谁在连”,不回答“连的是谁”。哪怕Origin合法、子协议匹配,也必须在连接建立后的第一个数据帧里完成用户身份绑定,否则攻击者仍可用合法页面+伪造token的方式穿透防线。

















