原生 websocket 构造函数不支持自定义请求头,因 w3c 规范禁止传入 headers 参数,握手请求由浏览器内核自动生成且仅允许 sec-websocket-protocol 这一特例;替代方案包括 url 参数、首次消息认证、反向代理注入或子协议伪装。

原生 WebSocket 构造函数不支持传入自定义请求头——这是浏览器强制限制,不是库或写法问题。所有“在握手阶段加 header”的需求,必须绕过原生 API 或借助服务端/代理协同实现。
为什么原生 WebSocket 无法设置 headers
W3C 规范明确禁止 new WebSocket(url, protocols) 接收 headers 参数。握手请求由浏览器内核自动生成,开发者无权干预其 HTTP 头部字段(除 Sec-WebSocket-Protocol 这个特例)。试图用 fetch 或 XMLHttpRequest 模拟握手也不会成功,因为 WebSocket 升级必须由底层网络栈发起。
- 常见错误现象:
TypeError: Failed to construct 'WebSocket': The subprotocol is invalid—— 误把 headers 当成protocols数组传入 - 浏览器控制台 network 面板里能看到握手请求,但 header 区域永远只有
Upgrade、Connection、Sec-WebSocket-Key等固定字段 - 即使使用
WebSocket.prototype原型注入方法,也无法影响实际发出的请求头
Python websocket-client 如何安全注入 headers
Python 的 websocket-client 库允许重写握手头生成逻辑,但必须注意 key 生成与字段顺序——部分后端(尤其是反爬场景)会校验 header 排序或 Sec-WebSocket-Key 格式。
- 核心操作是 monkey patch
_handshake._get_handshake_headers函数 -
Sec-WebSocket-Key必须是 16 字节随机数据 Base64 编码结果,不能硬编码固定值,否则服务端拒绝连接 - 自定义 header 如
Authorization或X-Client-ID必须插入到返回的headers列表中,且位置不能破坏协议必需字段的顺序 - 示例关键代码片段:
from websocket import _handshake
import base64
import os
<p>def get_handshake_headers(resource, url, host, port, options):
key = base64.b64encode(os.urandom(16)).decode('ascii')
headers = [
f'GET {resource} HTTP/1.1',
f'Host: {host}',
'Connection: Upgrade',
'Upgrade: websocket',
f'Sec-WebSocket-Key: {key}',
'Sec-WebSocket-Version: 13',
'Authorization: Bearer abc123', # ← 自定义 header 插入此处
'X-Client-Version: 2.1.0',
]
return headers, key</p><p>_handshake._get_handshake_headers = get_handshake_headers</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/gongju/2287" title="WebSocket 8.18.2"><img
src="https://img.php.cn/upload/manual/001/221/864/6a1560869301b447.png" alt="WebSocket 8.18.2" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/gongju/2287" title="WebSocket 8.18.2" class="overflowclass">WebSocket 8.18.2</a>
<p class="overflowclass">WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。</p>
</div>
<a rel="nofollow" href="/xiazai/gongju/2287" title="WebSocket 8.18.2" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
前端绕过限制的三种可行路径
当服务端坚持要求 header 中携带 token 或认证信息时,前端只能选择非原生方案或架构层配合。没有银弹,只有适配场景的选择。
-
URL 查询参数:最简单,适用于 token 较短、不涉敏感信息的场景;
wss://api.example.com/ws?token=xxx;注意长度限制(通常 2KB 左右)和日志泄露风险 -
首次消息认证:连接建立后立刻
send(JSON.stringify({type:"auth", token:"xxx"}));服务端需在onOpen后等待该消息并验证,超时断连;适合需要动态 token 或刷新机制的系统 -
反向代理注入:Nginx 或 Envoy 在转发 WebSocket 请求时,从 URL 参数或 cookie 提取值,用
proxy_set_header注入真实 header;例如proxy_set_header X-Auth-Token $arg_token;;这是唯一能让服务端收到标准 header 的纯前端可控方式
Sec-WebSocket-Protocol 是唯一可写的“伪 header”
Sec-WebSocket-Protocol 是唯一被协议允许、且浏览器允许前端指定的 header 字段。它本质是子协议协商机制,但常被滥用于传递简短标识(如 token 片段、租户 ID)。
- 用法:
new WebSocket('wss://...', ['v2.auth:abc123']) - 服务端必须原样回传该值到
Sec-WebSocket-Protocol响应头,否则连接失败 - 长度受限(HTTP header 行总长一般 ≤ 8KB),且语义上不属于认证字段,不适合长 token 或敏感凭证
- Node.js ws 库获取方式:
ws.upgradeReq.headers['sec-websocket-protocol'];Spring Boot 需通过HandshakeRequest.getHeaders()
真正难的不是“怎么加 header”,而是判断哪一层该承担这个责任:前端硬塞、代理补全、还是服务端降级接受 URL 或首包消息?多数线上问题卡在没分清这三者的边界。










