必须显式设置checkorigin函数,禁用nil默认行为;生产环境应基于scheme+host两级白名单校验origin,拒绝空或非法origin,禁止通配符和耗时操作,且校验须在upgrade前完成。

CheckOrigin函数必须显式设置,不能依赖nil默认行为
gorilla/websocket的Upgrader.CheckOrigin字段为nil时,会启用checkSameOrigin——它只允许Origin头与Host头完全一致(不区分大小写)的请求。这在生产环境几乎不可用:前端通常部署在https://app.example.com,后端WebSocket服务跑在wss://api.example.com,两者Host不同,直接被拒。
更危险的是,有人为“快速验证”把CheckOrigin设成func(r *http.Request) bool { return true },等于彻底关闭防护,暴露CSWSH(跨站WebSocket劫持)风险。
- 浏览器自动注入
Origin头,无法被前端JS伪造,所以白名单校验是可信的防线 - 必须解析
Origin字符串为URL,再比对u.Scheme + "://" + u.Host,不能只比u.Host(否则http://evil.com和https://example.com会被误判相同) - 空
Origin(如file://页面)应按场景决定是否放行,开发环境可接受,生产环境建议拒绝或记录告警
白名单需支持协议+域名两级匹配,且区分环境
硬编码白名单字符串(如"https://example.com")看似简单,但实际容易漏掉子域、协议变体或本地调试地址。正确做法是提取Origin的scheme和host后,再查表。
以下是一个兼顾安全与灵活性的实现:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
origin := r.Header.Get("Origin")
if origin == "" {
// 生产环境建议返回 false;开发可加开关控制
return false
}
u, err := url.Parse(origin)
if err != nil {
return false
}
// 构造标准化 origin 字符串:scheme://host
standardOrigin := u.Scheme + "://" + u.Host
allowed := map[string]bool{
"https://example.com": true,
"https://app.example.com": true,
"https://admin.example.com": true,
// 开发环境可额外加: "http://localhost:3000", "http://127.0.0.1:5173"
}
return allowed[standardOrigin]
},
}
- 用
map[string]bool替代[]string遍历,O(1)查找,避免线性扫描影响握手性能 - 禁止在白名单中混入通配符(如
"https://*.example.com"),gorilla不解析通配符,必须显式列出所有合法组合 - 本地开发用
http://localhost:xxx时,务必确保该条目**仅存在于开发配置**,CI/CD流程中应自动剔除或报错
Origin校验必须在Upgrade之前完成,且不可绕过
WebSocket连接建立是HTTP升级过程:Upgrade: websocket请求进来 → 服务端校验 → 调用upgrader.Upgrade() → 连接就绪。任何认证、鉴权、Origin检查都必须在这个调用之前做完。
常见错误是把校验逻辑放在conn.ReadMessage()之后,或者试图在WebSocket连接建立后再读取token——此时连接已建立,攻击者早已拿到长连接句柄,校验失去意义。
- Origin头只存在于升级请求中,连接建立后
conn对象不再携带该信息 - 如果还需用户身份,应在Upgrade前从
r.URL.Query().Get("token")或r.Header.Get("Authorization")提取并校验JWT/session - 不要在
CheckOrigin里做耗时操作(如DB查询、远程HTTP调用),它运行在HTTP handler goroutine中,阻塞会拖慢整个握手流程
别混淆CORS响应头和WebSocket Origin校验
很多人以为给响应加Access-Control-Allow-Origin: *就能解决WebSocket 403,这是根本性误解。CORS机制**不适用于WebSocket**:浏览器发起WebSocket连接时,不会发送预检OPTIONS请求,也不检查服务端返回的CORS头;它只看Origin请求头,并由服务端在CheckOrigin中决定是否放行。
你看到的403错误,来自Upgrader.Upgrade()内部对CheckOrigin返回值的判断,和HTTP响应头无关。
- 删掉所有试图用
w.Header().Set("Access-Control-Allow-Origin", "...")修复WebSocket跨域的代码,它完全无效 - 如果你同时提供REST API和WebSocket服务,CORS中间件(如
gorilla/handlers.CORS)只作用于HTTP路由,不影响WebSocket路径 - 真正要监控的指标是
CheckOrigin函数的拒绝日志——比如高频出现http://evil-site.net,说明有扫描或攻击尝试










