在Go语言开发WebSocket服务的过程中,客户端发起连接时返回403状态码是较为常见的问题,这种情况大多和Origin校验机制相关。WebSocket协议在握手阶段会携带Origin头,服务端默认会对该头信息进行校验,若不符合预设规则就会拒绝连接并返回403错误。

Origin校验的基本逻辑
Origin头用于标识请求发起的来源站点,服务端通过校验该字段可以避免跨站WebSocket劫持攻击。Go语言中常用的WebSocket实现有标准库golang.org/x/net/websocket和第三方库github.com/gorilla/websocket,两者的默认校验逻辑存在差异。
标准库默认校验行为
标准库的websocket包在创建Handler时,会默认校验Origin头,如果Origin为空或者不在允许的列表中,就会直接返回403错误。
gorilla/websocket默认校验行为
gorilla/websocket的Upgrader结构体默认会校验Origin,若没有自定义CheckOrigin函数,当Origin不符合同源策略时也会返回403错误。
解决403 Origin校验问题的方案
方案一:自定义CheckOrigin函数允许特定来源
这种方式适合需要限制合法来源的场景,只放行指定的域名或者IP地址。
以gorilla/websocket为例,代码如下:
package main
import (
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
// 自定义Origin校验函数
CheckOrigin: func(r *http.Request) bool {
// 允许的Origin列表
allowedOrigins := []string{
"http://localhost:3000",
"http://127.0.0.1:3000",
"http://ipipp.com",
}
origin := r.Header.Get("Origin")
// 遍历允许的列表,匹配则返回true
for _, allowed := range allowedOrigins {
if origin == allowed {
return true
}
}
return false
},
}
func wsHandler(w http.ResponseWriter, r *http.Request) {
// 升级HTTP连接为WebSocket连接
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
http.Error(w, "升级连接失败", http.StatusInternalServerError)
return
}
defer conn.Close()
// 读取客户端消息并回显
for {
msgType, msg, err := conn.ReadMessage()
if err != nil {
break
}
conn.WriteMessage(msgType, msg)
}
}
func main() {
http.HandleFunc("/ws", wsHandler)
http.ListenAndServe(":8080", nil)
}
方案二:临时关闭Origin校验(仅开发环境使用)
在本地开发调试阶段,为了方便测试,可以暂时关闭Origin校验,但生产环境不建议使用这种方式,会带来安全风险。
同样以gorilla/websocket为例,代码如下:
package main
import (
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
// 开发环境临时关闭Origin校验,生产环境需删除
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func wsHandler(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
http.Error(w, "升级连接失败", http.StatusInternalServerError)
return
}
defer conn.Close()
for {
msgType, msg, err := conn.ReadMessage()
if err != nil {
break
}
conn.WriteMessage(msgType, msg)
}
}
func main() {
http.HandleFunc("/ws", wsHandler)
http.ListenAndServe(":8080", nil)
}
方案三:标准库自定义校验逻辑
如果使用标准库的golang.org/x/net/websocket,可以通过自定义Handler的校验逻辑解决403问题,代码如下:
package main
import (
"net/http"
"golang.org/x/net/websocket"
)
func wsHandler(ws *websocket.Conn) {
defer ws.Close()
// 读取客户端消息并回显
var msg string
for {
err := websocket.Message.Receive(ws, &msg)
if err != nil {
break
}
websocket.Message.Send(ws, "收到消息:"+msg)
}
}
func main() {
// 自定义Origin校验的逻辑
http.Handle("/ws", websocket.Handler(wsHandler))
// 启动服务前可以添加自定义的校验中间件
http.ListenAndServe(":8080", nil)
}
不同方案的适用场景
可以通过以下表格对比不同方案的适用情况:
| 方案 | 适用场景 | 安全性 |
|---|---|---|
| 自定义CheckOrigin允许特定来源 | 生产环境,需要限制合法客户端来源 | 高 |
| 临时关闭Origin校验 | 本地开发、调试阶段 | 低 |
| 标准库自定义校验 | 使用标准库websocket的场景 | 中高(需自行实现合理校验逻辑) |
注意事项
- 生产环境不要直接关闭Origin校验,否则可能被跨站劫持攻击,导致服务被恶意调用。
- 如果客户端是小程序、APP等非浏览器环境,可能不会携带Origin头,此时需要根据实际情况调整校验逻辑,比如允许Origin为空的情况。
- 若服务部署在反向代理之后,需要注意代理是否会修改Origin头,避免校验规则失效。