You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WebSocket+JWT认证登录流程异常,请求排查解决

WebSocket + JWT认证流程异常排查与修复

异常原因分析

  • 服务端重复绑定WebSocket的message事件:同一连接回调内先后绑定两个ws.on('message'),客户端每条消息都会触发两个回调。首次发送登录数据时,第二个回调会把JSON格式的登录信息当作JWT验证,直接抛出JsonWebTokenError: jwt must be a string错误。
  • 客户端重复覆盖onmessage回调:先定义接收JWT并回发的回调,后又定义处理验证结果的回调,后者覆盖前者,导致JWT回发逻辑未执行,浏览器日志无"second send from client(token)"输出,反而因新回调直接打印MessageEvent对象,出现give data from client [object MessageEvent]无效日志。
  • 异步操作顺序混乱:服务端数据库查询为异步操作,第一个message回调触发后,数据库查询未完成,第二个message回调已先处理登录数据报错,之后数据库查询才完成返回JWT,但客户端回调已被覆盖,无法正确处理。
  • 客户端变量作用域错误:最后一个onmessage回调调用jwt_decode(token)时,token变量未在当前作用域定义,导致解码失败。
  • 原代码缺失密码校验:服务端仅查询手机号是否存在,未验证密码,存在安全隐患。

解决方案

服务端代码修复

合并message事件处理逻辑,根据消息内容区分登录请求与JWT验证请求:

wss.on('connection', (ws) => {
  ws.on('message', (msg) => {
    try {
      // 尝试解析为JSON,判断是否为登录数据
      let data = JSON.parse(msg);
      if (data.mobile && data.password) {
        // 处理登录请求,新增密码校验
        database.query(`SELECT * FROM users WHERE mobile=?`, [data.mobile], (err, result) => {
          if (err) throw err;
          if (result.length > 0) {
            // 验证密码正确性
            if (result[0].password === data.password) {
              const uuid = result[0].uuid;
              const token = jwt.sign({
                mobile: data.mobile,
                uuid: uuid
              }, secretOrPrivateKey, { expiresIn: '7d' });
              console.log(`send token from server : ${token}`);
              ws.send(token);
            } else {
              ws.send('false');
            }
          } else {
            ws.send('false');
          }
        });
      }
    } catch (e) {
      // 解析失败,判定为JWT字符串,处理验证请求
      console.log('this msg for JWT verification : ' + msg);
      try {
        const decoded = jwt.verify(msg, secretOrPrivateKey);
        console.log(`data have value`);
        ws.send('true');
      } catch(err) {
        console.log(err);
        ws.send('false');
        console.log(`data is not create in server for JWT verification`);
      }
    }
  });
});

客户端代码修复

统一onmessage回调,区分处理不同类型消息,修复变量作用域与日志问题:

// 初始化WebSocket连接(替换为你的服务端地址)
const ws = new WebSocket('ws://localhost:3000');

// 登录按钮点击事件
loginBtn.addEventListener('click', (e) => {
  e.preventDefault();
  const data = {
    mobile: mobile.value,
    password: password.value
  };
  console.log(`first send from client`);
  ws.send(JSON.stringify(data));
});

// 统一处理WebSocket消息
ws.onmessage = (msg) => {
  const receivedData = msg.data;
  console.log(`received from server: ${receivedData}`);

  // 判断消息类型:JWT令牌(含.分隔符)或验证结果
  if (receivedData.includes('.')) {
    // 收到JWT,存储后回发验证
    localStorage.setItem('jwt', receivedData);
    console.log(`second send from client(token) : ${receivedData}`);
    ws.send(receivedData);
  } else {
    // 收到验证结果
    if (receivedData === 'false') {
      errorMsg.innerHTML = '账号或密码错误!';
    } else if (receivedData === 'true') {
      try {
        // 从localStorage获取token解码
        const token = localStorage.getItem('jwt');
        const decodedData = jwt_decode(token);
        location.href = `http://localhost:3000/messaging/${decodedData.uuid}`;
      } catch (err) {
        console.log('JWT解码失败:', err);
      }
    }
  }
};

// JWT解码方法
function jwt_decode(token) {
  const base64Url = token.split('.')[1];
  const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
  const jsonPayload = decodeURIComponent(atob(base64).split('').map(c => {
    return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
  }).join(''));
  return JSON.parse(jsonPayload);
}

验证流程说明

  1. 客户端点击登录,发送JSON格式的账号密码
  2. 服务端解析JSON,校验账号密码,返回JWT或false
  3. 客户端收到JWT后存储到localStorage,再回发给服务端
  4. 服务端验证JWT有效性,返回true或false
  5. 客户端收到true后解码JWT,跳转到目标页面;收到false则显示错误提示

内容的提问来源于stack exchange,提问作者arta

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 12:07:20