React中带JWT刷新令牌的SignalR客户端断连重连问题咨询
问题描述
我有一个已经实现JWT认证的SignalR聊天应用,切换为Refresh Token认证方案后,当授权令牌过期时客户端SignalR连接会断开,但我检测不到断连状态:我已经尝试监听connection.onclose()事件以及服务端触发的connection.on("Disconnected")事件,二者都没有触发打印日志,因此无法刷新授权令牌并重建SignalR连接。以下是我的客户端代码、控制台截图及网络层截图:
控制台截图
网络层事件源截图
原有客户端代码:
connection = new signalR.HubConnectionBuilder() .withUrl( `${BASE_URL}/chat` ,{ accessTokenFactory: () => localStorage.getItem("user") && JSON.parse(localStorage.getItem("user")).token, } ) .build(); const start = async () => { connection .start(() => console.log("started")) .then((res) => { console.log("connection done"); }) .catch((err) => { console.log(err, "connection error"); if (err.statusCode === 401) { let user = JSON.parse(localStorage.getItem("user")); console.log("Auth Error"); RestService.ReAuthenticateUser(user.token, user.refreshToken) .then((res) => { let { data } = res; console.log(res); if (data.isSuccess) { user.token = data.data.token; user.refreshToken = data.data.refreshToken; localStorage.setItem("user", JSON.stringify(user)); connection = new signalR.HubConnectionBuilder() .withUrl(`${BASE_URL}/chat`, { accessTokenFactory: () => localStorage.getItem("user") && JSON.parse(localStorage.getItem("user")).token, }) .build(); connection .start(() => console.log("started")) .then((res) => { console.log("connection done"); }) .catch((err) => { localStorage.clear(); window.location.reload(false); }); } else { if (data.message != "This token hasn't expired yet") { localStorage.clear(); window.location.reload(false); } else { connection = new signalR.HubConnectionBuilder() .withUrl(`${BASE_URL}/chat`, { accessTokenFactory: () => localStorage.getItem("user") && JSON.parse(localStorage.getItem("user")).token, }) .build(); connection .start(() => console.log("started")) .then((res) => { console.log("connection done"); }) .catch((err) => { localStorage.clear(); window.location.reload(false); }); } } }) .catch((err) => { localStorage.clear(); window.location.reload(false); }); } }); }; start(); connection.on("Disconnected", (userId) => { console.log("Disconnected signalR");//This is not consoling start() }); connection.onclose(() => { console.log("Disconnected signalR");//This is not consoling start() });
解决方案
核心问题原因
- 你在token刷新成功后重新创建了
HubConnection实例,但没有为新实例重新绑定onclose和自定义的Disconnected事件,所有事件监听都只绑定在初始创建的旧实例上,自然不会触发日志 signalR.HubConnection的start()方法不支持传入回调函数,你写的回调逻辑完全不会执行- 你没必要每次认证失败就重新创建
HubConnection实例,accessTokenFactory会在每次SignalR发起请求(包括握手、重连)时自动调用,只要你刷新token后更新了localStorage里的存储值,下次请求会自动携带新token
修正后代码
// 封装连接初始化逻辑,统一绑定事件 const initConnection = () => { const connection = new signalR.HubConnectionBuilder() .withUrl(`${BASE_URL}/chat`, { accessTokenFactory: () => { const user = localStorage.getItem("user"); return user ? JSON.parse(user).token : ""; }, }) // 开启内置自动重连,按0/1/3/5秒间隔尝试重连,不需要手动写基础重连逻辑 .withAutomaticReconnect([0, 1000, 3000, 5000]) .build(); // 事件绑定统一放在初始化逻辑里,保证所有实例都能触发事件回调 connection.on("Disconnected", (userId) => { console.log("收到服务端断连通知,用户ID:", userId); startConnection(connection); }); connection.onclose((error) => { console.log("连接已断开,错误信息:", error); startConnection(connection); }); connection.onreconnecting((error) => { console.log("正在尝试自动重连,错误信息:", error); }); connection.onreconnected((connectionId) => { console.log("重连成功,新连接ID:", connectionId); }); return connection; }; let connection = initConnection(); const startConnection = async (conn) => { try { await conn.start(); console.log("连接建立成功"); } catch (err) { console.log("连接建立失败,错误信息:", err); if (err.statusCode === 401) { try { const userStr = localStorage.getItem("user"); if (!userStr) { localStorage.clear(); window.location.reload(false); return; } const user = JSON.parse(userStr); const res = await RestService.ReAuthenticateUser(user.token, user.refreshToken); const { data } = res; if (data.isSuccess) { // 仅更新localStorage即可,不需要新建连接实例 user.token = data.data.token; user.refreshToken = data.data.refreshToken; localStorage.setItem("user", JSON.stringify(user)); // 直接重启现有连接 await startConnection(conn); } else { if (data.message != "This token hasn't expired yet") { localStorage.clear(); window.location.reload(false); } else { await startConnection(conn); } } } catch (authErr) { localStorage.clear(); window.location.reload(false); } } } }; // 首次启动连接 startConnection(connection);
额外注意事项
- 服务端需要配置,当检测到token过期时主动向客户端发送合法的WebSocket关闭帧,否则客户端可能会认为连接是异常中断,延迟触发
onclose事件 - 如果你的JWT是短有效期,可以在token过期前提前主动调用刷新接口,避免出现连接断开的情况
- 如果使用Server-Sent Event或者长轮询传输协议,token过期会在下次轮询请求时返回401,此时会直接触发
onclose事件,不需要额外处理
内容的提问来源于stack exchange,提问作者momin naveed
相关产品推荐
相关产品推荐

