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 传参更安全)。

相关推荐
镜宇秋霖丶1 小时前
2026.5.10@霖宇博客制作中遇见的问题
前端·vue.js·elementui
进阶的猿猴2 小时前
Rsa简单实现接口到期限制(springBoot)
java·spring boot·后端
花花鱼2 小时前
Spring Framework 、Spring Boot 、 Spring Data 、Spring Cloud之间的关系简单说明
spring boot·spring·spring cloud
晓杰'2 小时前
从0到1实现 Balatro 游戏后端(1):项目规划与牌型判断实现
后端·websocket·typescript·node.js·游戏开发·项目实战·nestjs
spmcor2 小时前
Vue 3 知识点完全梳理:20+ 核心特性一网打尽
vue.js
Momo__2 小时前
Vue 3.4+ 被低估的 3 个 API,让你的代码更优雅
前端·vue.js
Csvn2 小时前
组合式函数
前端·vue.js
一只IT攻城狮3 小时前
️ Spring Boot 文件上传,防御恶意文件攻击
java·spring boot·web安全
倒流时光三十年3 小时前
第6篇 Consumer 精讲(上):Offset 提交与幂等消费
spring boot·kafka