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

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()
  });

解决方案

核心问题原因

  1. 你在token刷新成功后重新创建了HubConnection实例,但没有为新实例重新绑定onclose和自定义的Disconnected事件,所有事件监听都只绑定在初始创建的旧实例上,自然不会触发日志
  2. signalR.HubConnection的start()方法不支持传入回调函数,你写的回调逻辑完全不会执行
  3. 你没必要每次认证失败就重新创建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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 08:51:00