websocket连接前必须完成token鉴权,服务端须在upgrade阶段校验url参数或sec-websocket-protocol头中的token,无效则立即返回401;推荐反向代理注入authorization头并结合短期token与redis黑名单实时校验吊销状态。

WebSocket连接前必须完成Token鉴权,不能靠连接后再发登录消息
握手阶段不校验Token,等于放行未授权长连接——攻击者可复用空闲连接发起广播、耗尽服务端资源或伪造身份。浏览器 WebSocket 构造函数(new WebSocket())本身不支持传 headers,所以“在连接建立后 send 一个 Authorization 消息”是常见但错误的做法。服务端必须在 upgrade 请求处理中就拒绝非法请求,返回 HTTP 401 并终止 TCP 连接。
关键判断点:只要 req.url 或 req.headers 中没拿到有效 Token,就该直接 res.writeHead(401) + res.end(),不要调用 wsServer.handleUpgrade()。
URL参数传Token最简单,但只适用于服务端明确解析 query 的场景
前端写法:new WebSocket("wss://api.example.com/ws?token=eyJhbGciOiJ...");服务端需手动从 req.url 解析查询字符串,例如 Node.js 的 url.parse(req.url, true).query.token,或 Go 的 req.URL.Query().Get("token")。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 优点:浏览器原生支持,无需 polyfill 或代理层,Hoppscotch / wscat 等工具开箱即用
- 缺点:Token 会出现在浏览器地址栏、服务器 access log、CDN 日志、中间代理记录中;不适合高敏感系统
- 注意:若 Token 含特殊字符(如
+、/、=),前端应先encodeURIComponent(),服务端再decode;否则可能被截断或解析失败
Sec-WebSocket-Protocol 头可作为轻量级载体,但需服务端主动读取
这不是标准认证头,而是 WebSocket 协议定义的子协议协商字段,但很多服务端(尤其是自研网关或轻量框架)会复用它传 Token。前端写法:new WebSocket("wss://api.example.com/ws", ["Bearer eyJhbGciOiJ..."]);服务端需从 req.headers["sec-websocket-protocol"] 提取值并校验。
- 不依赖
Authorization头,绕过浏览器对普通 WebSocket 的 header 限制 - Hoppscotch 支持通过 “Add Protocol” 添加,Postman WebSocket 也支持填入 Protocol 字段
- 风险:若服务端未做白名单校验(比如只允许
chat-v1、notify-v2这类固定协议名),可能被恶意构造协议名绕过
真正安全的方式:反向代理注入 Authorization 头 + 服务端统一校验
前端仍用 URL 参数或子协议传 Token,但真实生产环境应在 Nginx / Traefik 层将其转为标准 Authorization: Bearer xxx 头,再透传给后端。这样既兼容前端限制,又让服务端逻辑回归标准 JWT 校验流程(如 Go 的 golang-jwt/jwt/v5 默认只认这个头)。
- Nginx 配置示例:
proxy_set_header Authorization "Bearer $arg_token";(从 URL 参数提取)或proxy_set_header Authorization "Bearer $http_sec_websocket_protocol";(从子协议头提取) - 服务端不再需要特殊解析逻辑,可复用现有 HTTP 中间件(如 Express 的
express-jwt、Spring Security 的BearerTokenResolver) - 必须配合短期 Token(15–30 分钟有效期)+ Redis 黑名单,避免 Token 泄露后长期有效
最容易被忽略的一点:Token 吊销状态必须在握手时实时检查。哪怕签名和时效都合法,如果用户已在别处登出,服务端仍应查一次缓存(如 redis.get("token:blacklist:" + hash(token)))再决定是否放行连接。










