Vue + SpringBoot 实现 WebSocket 基于 Sec-WebSocket-Protocol 传参鉴权(避坑指南)

在 WebSocket 开发中,很多开发者会遇到一个常见误区:想像 HTTP 请求那样自定义任意请求头(比如 Authorization 鉴权),但实际上浏览器原生 WebSocket API 并不支持这一操作。本文将重点讲解 WebSocket 唯一官方允许的自定义"伪请求头"------Sec-WebSocket-Protocol 的具体实现,结合 Vue 前端与 SpringBoot 后端,提供可直接复用的核心代码。

一、核心前提:浏览器原生 WebSocket 的传参限制

浏览器原生 WebSocket API 不支持自定义任意请求头,不像 axios、fetch 那样可以通过 headers 配置项添加 Authorization、X-User-Id 等自定义头。

浏览器原生 WebSocket 仅支持两种官方传参方式:

  1. URL 查询参数(如 ws://localhost:8080/ws?token=xxx),兼容性最好,但参数会暴露在 URL 中,安全性一般;【var ws = new WebSocket("ws://url?userid=1");】

  2. Sec-WebSocket-Protocol 子协议,本质是 WebSocket 握手阶段的请求头字段,可用于传递鉴权信息,比 URL 参数更规范、更安全。

注意:我们平时看到的 WebSocket 自定义 headers 写法,大多是** socket.io、ws 等第三方库封装的**,并非原生 API 支持,纯原生开发需避开这一误区。

二、Sec-WebSocket-Protocol 核心原理

Sec-WebSocket-Protocol 是 WebSocket 官方定义的请求头字段,用于指定客户端与服务器之间的通信子协议。其核心作用的延伸的是:可将鉴权信息(如 Token)作为子协议值传入,后端在握手阶段获取该请求头,实现鉴权逻辑

工作流程:

  1. 前端创建 WebSocket 实例时,传入第二个参数(子协议值);

  2. 浏览器自动将该参数写入请求头 Sec-WebSocket-Protocol;

  3. 后端在 WebSocket 握手阶段(OnOpen 方法)获取该请求头(Sec-WebSocket-Protocol),解析出鉴权信息;

  4. 后端完成鉴权后,正常建立 WebSocket 连接。

限制:Sec-WebSocket-Protocol 的值仅支持英文字母、数字、短横线,不能包含中文或特殊符号(如空格、@、# 等),因此传递 Token 时需注意格式。

三、Vue 前端核心实现(原生 WebSocket,无第三方库)

代码基于 Vue3(Setup 语法)实现,Vue2 写法类似,核心逻辑一致------通过 WebSocket 实例的第二个参数传递子协议(携带 Token)。

前端代码格式:

let socket = new WebSocket(url,protocols);

后台接收到websocket连接后,可以通过下述代码获取到token值:

request.getHeader("Sec-WebSocket-Protocol")

核心代码:

TypeScript 复制代码
<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 支持)

java 复制代码
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-Protocol)

java 复制代码
import 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 ***
        List<String> 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() 获取所有请求头,再通过 key Sec-WebSocket-Protocol 取出前端传入的子协议值;**

  • 无需引入第三方 WebSocket 依赖(SpringBoot 原生支持),仅需保证项目依赖中包含 spring-websocket(一般 SpringBoot -web 依赖已集成)。

五、避坑要点总结

  1. 不要试图在浏览器原生 WebSocket 中使用 headers 配置自定义请求头,官方不支持,会无效;

  2. Sec-WebSocket-Protocol 的值不能包含中文、特殊符号,否则会导致连接失败;

  3. 前端传入的子协议格式,需与后端解析逻辑一致(如前端用 Bearer- 前缀,后端需对应截取);

  4. 后端必须在 OnOpen 方法(握手阶段)获取 Sec-WebSocket-Protocol,连接建立后无法再获取该请求头;

  5. 该方案纯原生实现,无第三方依赖,兼容性好,适合生产环境鉴权(比 URL 传参更安全)。

相关推荐
卤蛋fg62 小时前
vxe-table 实现数据分组统计与表尾合计
vue.js
TickDB3 小时前
智谱GLM-4 接金融数据:工具描述多写三个字,模型少犯一类错
人工智能·python·websocket·行情数据 api·行情 api
学代码的真由酱3 小时前
WebSocket背景知识及简单实现-Java
java·websocket
鹏多多4 小时前
OpenSpec+SDD规范驱动AI Agent开发项目实战指南
前端·vue.js·react.js
wjj不想说话4 小时前
你项目里的 Pinia,可能已经成了第二个 localStorage
前端·vue.js
Java程序员-小白6 小时前
Spring Boot整合Sa-Token框架(入门篇)
java·spring boot·后端·sa-token
小楊不秃头6 小时前
SpringBoot: IoC&DI
spring boot·ioc·di
绝知此事6 小时前
ELK 从入门到精通:Spring Boot 实战三部曲(三)—— 高级应用与架构设计
spring boot·后端·elk
Devin~Y7 小时前
从内容社区到AIGC客服:Spring Boot、Redis、Kafka、K8s、RAG的三轮大厂Java面试对话(附标准答案)
java·spring boot·redis·spring cloud·kafka·kubernetes·micrometer