在 WebSocket 开发中很多开发者会遇到一个常见误区想像 HTTP 请求那样自定义任意请求头比如 Authorization 鉴权但实际上浏览器原生 WebSocket API 并不支持这一操作。本文将重点讲解 WebSocket 唯一官方允许的自定义“伪请求头”——Sec-WebSocket-Protocol 的具体实现结合 Vue 前端与 SpringBoot 后端提供可直接复用的核心代码。一、核心前提浏览器原生 WebSocket 的传参限制浏览器原生 WebSocket API 不支持自定义任意请求头不像 axios、fetch 那样可以通过 headers 配置项添加 Authorization、X-User-Id 等自定义头。浏览器原生 WebSocket 仅支持两种官方传参方式URL 查询参数如 ws://localhost:8080/ws?tokenxxx兼容性最好但参数会暴露在 URL 中安全性一般【var ws new WebSocket(ws://url?userid1);】Sec-WebSocket-Protocol 子协议本质是 WebSocket 握手阶段的请求头字段可用于传递鉴权信息比 URL 参数更规范、更安全。注意我们平时看到的 WebSocket 自定义 headers 写法大多是** socket.io、ws 等第三方库封装的**并非原生 API 支持纯原生开发需避开这一误区。二、Sec-WebSocket-Protocol 核心原理Sec-WebSocket-Protocol 是 WebSocket 官方定义的请求头字段用于指定客户端与服务器之间的通信子协议。其核心作用的延伸的是可将鉴权信息如 Token作为子协议值传入后端在握手阶段获取该请求头实现鉴权逻辑。工作流程前端创建 WebSocket 实例时传入第二个参数子协议值浏览器自动将该参数写入请求头 Sec-WebSocket-Protocol后端在 WebSocket 握手阶段OnOpen 方法获取该请求头(Sec-WebSocket-Protocol)解析出鉴权信息后端完成鉴权后正常建立 WebSocket 连接。限制Sec-WebSocket-Protocol 的值仅支持英文字母、数字、短横线不能包含中文或特殊符号如空格、、# 等因此传递 Token 时需注意格式。三、Vue 前端核心实现原生 WebSocket无第三方库代码基于 Vue3Setup 语法实现Vue2 写法类似核心逻辑一致——通过 WebSocket 实例的第二个参数传递子协议携带 Token。前端代码格式let socket new WebSocket(url[protocols]);后台接收到websocket连接后可以通过下述代码获取到token值request.getHeader(Sec-WebSocket-Protocol)核心代码script setup import { onMounted } from vue; onMounted(() { // 1. 从本地存储localStorage获取鉴权 Token const token localStorage.getItem(token) || ; // 2. WebSocket 连接地址 const wsUrl ws://localhost:8080/ws; // 3. 第二个参数传入子协议格式自定义此处用 Bearer- 前缀区分 Token // 支持字符串单个值或数组多个值 const ws new WebSocket(wsUrl, Bearer-${token}); // 4. 监听 WebSocket 连接状态 ws.onopen () { console.log(WebSocket 连接成功已通过 Sec-WebSocket-Protocol 传入 Token); // 连接成功后可发送消息 ws.send(客户端已连接); }; ws.onmessage (e) { console.log(收到后端消息, e.data); }; ws.onerror (err) { console.error(WebSocket 连接失败/错误, err); }; ws.onclose () { console.log(WebSocket 连接关闭); }; }) /script关键说明第二个参数Bearer-${token}可自定义格式如token-${token}只要后端解析时对应即可无需引入任何第三方 Socket 库纯原生 WebSocket兼容性覆盖所有现代浏览器Token 从 localStorage 获取实际开发中可结合 Vuex/Pinia 管理登录状态。四、SpringBoot 后端核心实现原生 WebSocket无第三方库后端基于 SpringBoot 原生 WebSocket 实现核心是在 WebSocket 握手阶段OnOpen 方法获取请求头 Sec-WebSocket-Protocol解析出 Token 并完成鉴权。4.1 WebSocket 配置类开启 WebSocket 支持import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; /** * WebSocket 核心配置类 * 开启 SpringBoot 对 WebSocket 的支持 */ Configuration public class WebSocketConfig { Bean public ServerEndpointExporter serverEndpointExporter() { // 自动注册 ServerEndpoint 注解的 WebSocket 服务端点 return new ServerEndpointExporter(); } }4.2 WebSocket 服务端点核心获取 Sec-WebSocket-Protocolimport javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import javax.websocket.EndpointConfig; import java.util.List; /** * WebSocket 服务端点 * 路径/ws与前端连接地址对应 */ ServerEndpoint(/ws) public class WebSocketServer { /** * 连接建立时触发握手阶段 * param session WebSocket 会话 * param config 端点配置用于获取请求头 */ OnOpen public void onOpen(Session session, EndpointConfig config) { // **** 获取请求头 Sec-WebSocket-Protocol *** ListString protocolList config.getHandshakeRequest().getHeaders().get(Sec-WebSocket-Protocol); if (protocolList ! null !protocolList.isEmpty()) { // 取出子协议值即前端传入的 Bearer-${token} String protocol protocolList.get(0); // 解析 Token与前端格式对应截取 Bearer- 前缀 String token protocol.replace(Bearer-, ); // Token 校验逻辑 boolean isAuth checkToken(token); if (!isAuth) { // 鉴权失败关闭连接 try { session.close(); } catch (Exception e) { e.printStackTrace(); } System.out.println(Token 鉴权失败拒绝连接); return; } // 鉴权成功 System.out.println(Token 鉴权成功客户端连接建立Token token); } else { // 未获取到 Sec-WebSocket-Protocol无 Token关闭连接 try { session.close(); } catch (Exception e) { e.printStackTrace(); } System.out.println(未传入 Token拒绝连接); } } /** * 接收客户端消息 * param message 客户端发送的消息 * param session 会话 */ OnMessage public void onMessage(String message, Session session) { System.out.println(收到客户端消息 message); // 后端响应消息 session.getAsyncRemote().sendText(后端已收到消息 message); } /** * 连接关闭时触发 */ OnClose public void onClose() { System.out.println(WebSocket 连接关闭); } /** * 模拟 Token 校验 * param token 前端传入的 Token * return 鉴权结果 */ private boolean checkToken(String token) { //模拟 Token 不为空即通过 return token ! null !token.isEmpty(); } }关键说明通过config.getHandshakeRequest().getHeaders()获取所有请求头再通过 keySec-WebSocket-Protocol取出前端传入的子协议值无需引入第三方 WebSocket 依赖SpringBoot 原生支持仅需保证项目依赖中包含 spring-websocket一般 SpringBoot -web 依赖已集成。五、避坑要点总结不要试图在浏览器原生 WebSocket 中使用 headers 配置自定义请求头官方不支持会无效Sec-WebSocket-Protocol 的值不能包含中文、特殊符号否则会导致连接失败前端传入的子协议格式需与后端解析逻辑一致如前端用 Bearer- 前缀后端需对应截取后端必须在 OnOpen 方法握手阶段获取 Sec-WebSocket-Protocol连接建立后无法再获取该请求头该方案纯原生实现无第三方依赖兼容性好适合生产环境鉴权比 URL 传参更安全。