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

基于Express/React/Mongoose的JWT登录系统Cookie刷新异常求助

问题分析与解决方案

核心问题

你遇到的问题本质是并发请求导致的Token冲突:React端重复发送2个携带旧Refresh Token的请求,第一个请求处理时会将旧Token从用户的refreshToken数组中移除并生成新Token;第二个请求到达时,旧Token已被服务端标记无效,导致查找用户失败,最终返回403并清空Cookie,引发后续认证异常。

同时原服务端代码存在两个关键逻辑缺陷:

  • 过早清除Cookie,导致第二个请求在新Cookie生效前仍携带旧Token
  • 异步校验逻辑存在重复响应风险,且未处理并发修改场景

修复方案

1. 前端:解决重复请求问题

首先要从根源阻止重复请求,避免并发冲突:

  • 检查useEffect等触发请求的逻辑,添加正确的依赖项,防止重复渲染触发请求
  • 实现请求锁机制,确保同一时刻只有一个Refresh请求在处理:
let isRefreshing = false;
async function triggerRefresh() {
  if (isRefreshing) return;
  isRefreshing = true;
  try {
    const res = await fetch('/api/refresh', { credentials: 'include' });
    // 处理响应逻辑
  } catch (err) {
    // 错误处理
  } finally {
    isRefreshing = false;
  }
}

2. 服务端:重构Refresh路由逻辑

调整Token校验、用户查找和Cookie设置的顺序,避免并发冲突:

const refresh = asyncHandler(async (req, res) => {
  const cookies = req.cookies;
  if (!cookies?.jwt) return res.status(401).json({ message: "Unauthorized user" });

  const refreshToken = cookies.jwt;
  let user;

  try {
    // 先解码Token获取用户名,避免并发修改导致用户查找失败
    const decoded = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET);
    user = await Users.findOne({ username: decoded.username }).exec();

    if (!user) return res.sendStatus(403);

    // 检查当前请求的Token是否仍在用户的有效Token列表中
    const isTokenValid = user.refreshToken.includes(refreshToken);
    if (!isTokenValid) {
      // Token已被失效,清空用户所有Refresh Token
      user.refreshToken = [];
      await user.save();
      return res.sendStatus(403);
    }

    // 生成新的Access和Refresh Token
    const accessToken = generateToken({
      "UserInfo": {
        "username": user.username,
        "role": user.role
      }
    }, "access");

    const newRefreshToken = generateToken({ username: user.username }, "refresh");

    // 更新用户Token列表:移除旧Token,添加新Token
    user.refreshToken = user.refreshToken.filter(rt => rt !== refreshToken);
    user.refreshToken.push(newRefreshToken);
    await user.save();

    // 设置新Cookie(自动覆盖旧Cookie,无需手动清除)
    res.cookie('jwt', newRefreshToken, { 
      httpOnly: true, 
      secure: true, 
      sameSite: 'None', 
      maxAge: 24 * 60 * 60 * 1000 
    });

    res.json({ role: user.role, accessToken });
  } catch (err) {
    if (err.name === 'JsonWebTokenError') {
      // Token无效时,清空该用户所有Refresh Token
      if (user) {
        user.refreshToken = [];
        await user.save();
      }
      return res.sendStatus(403);
    }
    res.sendStatus(500);
  }
});

关键优化点说明

  • 延迟Cookie操作:不再提前清除Cookie,而是用新Cookie直接覆盖旧Cookie,确保前端只有在请求成功后才会拿到有效Token
  • 优先解码Token:通过JWT解码获取用户名查找用户,避免因并发修改导致的用户查找失败
  • Token有效性校验:检查请求携带的Token是否仍在用户的有效列表中,处理并发场景下的Token失效问题
  • 统一错误处理:整合异步校验逻辑,避免重复响应的问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 09:13:23