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); }
验证流程说明
- 客户端点击登录,发送JSON格式的账号密码
- 服务端解析JSON,校验账号密码,返回JWT或
false - 客户端收到JWT后存储到localStorage,再回发给服务端
- 服务端验证JWT有效性,返回
true或false - 客户端收到
true后解码JWT,跳转到目标页面;收到false则显示错误提示
内容的提问来源于stack exchange,提问作者arta
相关产品推荐
相关产品推荐

