go服务端未配置checkorigin会导致websocket连接被403拒绝,因gorilla/websocket默认checkorigin直接返回false,与cors无关,必须在upgrader中显式覆盖该函数。

Go 服务端没配 CheckOrigin,WebSocket 连接就会被 403 拒绝——这不是前端问题,也不是 CORS 配错了,是 gorilla/websocket 默认安全策略生效了。
为什么浏览器报 origin not allowed 却不是 CORS 问题
这个错误只出现在 WebSocket 连接阶段(new WebSocket() 调用时),和 HTTP 的 Access-Control-Allow-Origin 完全无关。浏览器在发起 WebSocket 升级请求时会带上 Origin 头,而 gorilla/websocket.Upgrader 的默认 CheckOrigin 方法直接返回 false,不看值、不校验、不放行。
常见误操作包括:
- 在中间件里加
Access-Control-Allow-Origin头——对 WebSocket 升级请求完全无效 - 以为改了路由 handler 就能拦截并放行——
CheckOrigin在 handler 执行前就已触发 - 用
net/http原生升级逻辑但没注意到 Go 1.22+ 的Upgrader也要求显式设CheckOrigin
开发环境快速验证:临时放开所有 origin
仅用于本地调试,别上生产。重点是让连接先通,确认问题根源确实在此:
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
注意两点:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 必须在
upgrader.Upgrade()之前初始化该实例,不能每次请求都 new 一个没配CheckOrigin的 - 如果用了 Gin,确保
upgrader是包级变量或单例,不是函数内局部变量
生产环境必须白名单校验 origin
直接 return true 等于关闭 WebSocket 层面的来源控制,攻击者可伪造任意 Origin 发起连接。正确做法是严格比对:
- 检查
r.Header.Get("Origin")是否在预设 map 中,注意协议、域名、端口必须完全一致(http://localhost≠http://localhost:3000) - 显式处理
"null"场景(比如本地file://页面或某些测试工具触发) - 若前端走反向代理(Nginx / Traefik),确认代理透传了
Origin头,否则后端取到的是空或代理地址
示例白名单逻辑:
allowedOrigins := map[string]bool{
"https://app.example.com": true,
"https://staging.example.com": true,
"http://localhost:3000": true,
"null": true, // 显式允许 null origin
}
upgrader.CheckOrigin = func(r *http.Request) bool {
origin := r.Header.Get("Origin")
return allowedOrigins[origin]
}
带 credentials 的 WebSocket 请求要额外注意
如果前端 WebSocket 构造时用了 {credentials: 'include'}(虽不常见,但部分旧 SDK 或自定义封装会这么做),服务端必须确保:
-
CheckOrigin返回true时,对应 origin 必须是具体值,不能是"*" - 不能依赖
Origin头做身份认证——它可被客户端任意修改,真正鉴权应放在连接建立后的消息阶段 - 若需动态校验(如带 token 参数),可在
CheckOrigin里读r.URL.Query().Get("token"),但不要调用w.WriteHeader()或写响应体
最易被忽略的一点:CheckOrigin 函数里任何 panic 都会导致连接直接断开且无日志提示,建议加 defer/recover 或至少 log 出错 origin 值。










