WebSocket鉴权必须在HTTP Upgrade阶段完成,否则连接已建立、资源被占用、无法返回401;需在Upgrade前解析URL参数或Header中的JWT,校验exp/iat/签名并查黑名单,成功后将用户信息绑定至连接实例。

WebSocket高并发下的安全验证,核心就一条:鉴权必须在 HTTP Upgrade 阶段完成,不能等 connection 事件之后再校验——否则连接已建立,资源已被占用,攻击者早就能发帧、占内存、耗 FD。
为什么 upgrade 前不鉴权就是裸奔
常见错误现象包括:
- 前端调用
new WebSocket(url)后立刻收到open事件,但服务端还没看过 Token - 未授权连接持续存活数小时,被用于广播消息、压垮心跳逻辑或连接池耗尽
- 业务层收到第一条
message才开始 parse JWT,此时非法用户已拿到conn句柄,可反复重连复用
根本原因在于:WebSocket 升级后是纯二进制帧通道,HTTP 状态码(如 401)无法再返回;res.end() 或 http.Error() 必须发生在 Upgrade 请求响应阶段,晚一秒就失效。
URL 参数传 token 的实操要点(最通用)
这是浏览器兼容性最好、前端接入成本最低的方式,但必须满足三个硬约束:
-
token必须是短期专用凭证(建议 15 分钟),不能复用登录态长期 JWT - 服务端解析时必须检查
exp、iat、签名有效性,并查 Redis 黑名单确认未吊销 - 前端拼接 URL 时必须用
encodeURIComponent(),否则含.或+的 JWT 会解析失败
示例(前端):const socket = new WebSocket(`wss://api.example.com/ws?token=${encodeURIComponent(wsToken)}`);
示例(Node.js + ws):server.on('connection', (ws, req) => { const token = new URL(req.url, 'http://f').searchParams.get('token'); if (!isValidJwt(token)) return ws.close(4001, 'invalid token'); });
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
Header 方式怎么绕过浏览器限制
标准 WebSocket 构造函数不支持传 headers,直接写 new WebSocket(url, { headers: {} }) 会报错。可行路径只有两条:
- 用
fetch+Response.upgrade()(仅 Chromium 支持,Firefox 不支持) - 通过反向代理注入:Nginx 配置
proxy_set_header Authorization "Bearer $arg_token";,后端从req.headers.authorization读取
注意两个易错点:
-
Bearer中Bearer和token之间必须是一个空格,多一个少一个都会导致解析失败 - Token 本身不能被二次 URL 编码,否则签名校验失败
Go / Spring Boot / Tomcat 中的鉴权落地差异
不同语言/框架对 Upgrade 请求的访问粒度不同,但共同底线是:鉴权代码必须出现在 Upgrade 调用之前。
- Go:
r.URL.Query().Get("token")或r.Header.Get("Authorization")必须在upgrader.Upgrade(w, r, nil)前执行,失败用http.Error(w, ..., http.StatusUnauthorized) - Spring Boot:
HandshakeInterceptor.beforeHandshake()是唯一合法入口,attributes.put("userId", userId)把身份带入后续 session - Tomcat:
ServerEndpointConfig.Configurator.modifyHandshake()中读HandshakeRequest.getHeaders()或.getParameterMap(),拒绝则抛异常或设响应状态码
别把用户 ID 存全局 map,也别在 onMessage 里重复校验——连接建立后就没有 HTTP 上下文了,所有身份信息必须在握手阶段绑定到连接实例上。










