直接放行CheckOrigin返回true是最危险解法,因它完全放弃Origin校验防线;浏览器自动注入Origin值(如http://localhost:5173),与后端Host不一致时触发403;默认校验严格比对Origin和Host,开发中常失败;白名单需精确匹配协议、域名、端口,禁用通配符;无Origin客户端应结合IP白名单或Token认证,而非放行空Origin;不可行时须改用WSS+时效性Token替代校验。
为什么直接 return true 给 CheckOrigin 是最危险的解法
因为这等于关掉 websocket 握手阶段唯一有效的来源防护机制。浏览器自动注入的 origin 头是客户端真实来源的唯一可信标识,checkorigin 就是靠它做第一道过滤。一旦返回 true,任何网站(包括 https://evil.com)都能用用户已登录的 cookie 发起连接,完成跨站 websocket 劫持(cswsh)。
常见错误现象:
- 前端在
file://下双击打开 HTML,连不上,但以为是“没发 Origin” - 用 Vite/HMR 本地服务(
http://localhost:5173)连后端ws://localhost:8080,报 403 - 生产环境配了
*.example.com通配,结果https://malicious.example.com也能连上
CheckOrigin 白名单必须精确匹配协议、域名、端口
Origin 字符串是完整 URL:比如 http://localhost:3000、https://admin.yourapp.com:443、https://client.yourapp.com。少写协议、漏端口、多加斜杠或路径,都会导致合法请求被拒。
正确做法:
- 开发环境只列明具体地址:
http://localhost:3000、http://127.0.0.1:5173,不写http://localhost:*(端口通配无效) - 生产环境只放真实上线域名:
https://app.mycompany.com、https://staging.mycompany.com,不带路径,不加/ - 如果前端走
file://协议,r.Header.Get("Origin")返回空字符串或"null",需单独判断:origin == "" || origin == "null",再决定是否豁免(仅限开发)
非浏览器客户端(curl、IoT 设备)不发 Origin 怎么办
这类客户端根本不会发送 Origin 头,r.Header.Get("Origin") 恒为空字符串。白名单匹配必然失败,但也不能因此放行——那会把校验逻辑变成摆设。
安全替代方案:
- 对 CLI 工具或内部服务,改用
Authorization头 + JWT 或短期 Token 认证,把身份验证从握手阶段前移到 Upgrade 请求中 - 对嵌入式设备等无法加 Token 的场景,结合 IP 白名单(
r.RemoteAddr)+ TLS 客户端证书双向认证 - 绝对不要用
if origin == "" { return true }—— 这等于给所有无 Origin 请求开后门
WSS + 时效性 Token 才是生产环境兜底方案
Origin 校验本质依赖浏览器行为,天生不适用于非浏览器场景,也防不住中间人伪造 Origin(虽然难,但不是不可能)。真正的生产级防护必须叠加传输层和应用层双重保险。
关键点:
- 强制使用
wss://,禁用ws://(哪怕只在开发环境留着,也常被误部署到生产) - Token 必须有时效性(如 5 分钟),且一次性(用过即废)或绑定连接 ID
- Token 不应放在 URL 查询参数里(易泄露、进日志),而应通过
Authorization头或 Upgrade 请求的自定义 header 传递 - Origin 白名单 + WSS + Token 三者不是“选一个”,而是“全都要”——它们防御的是不同攻击面
最容易被忽略的一点:很多人以为配好 CheckOrigin 就万事大吉,其实只要没关掉 ws://、没加 Token、没强制 TLS,Origin 白名单就只是纸糊的墙。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











