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

基于Redis的JWT静默刷新问题(MERN栈)

问题根源

React Strict Mode在开发环境会重复触发组件副作用(比如API请求),导致刷新接口被连续调用两次。第一次请求处理时,你执行了await redisClient.del(redisValue)删除旧refreshToken对应的Redis记录;而第二次请求时,客户端还没来得及更新cookie,依然携带旧refreshToken,此时Redis中已无对应条目,所以返回No refresh token in db。

代码问题分析
  1. JWT验证逻辑混乱:在async函数里混用jwt.verify的回调版本,导致代码嵌套复杂,容易引发异步执行顺序问题。
  2. Redis操作过于激进:立即删除旧refreshToken,没有考虑开发环境下的重复请求场景,也没给客户端更新cookie留缓冲时间。
解决方案

1. 改用JWT验证的异步版本

用util.promisify将jwt.verify转为Promise风格,配合async/await使用,避免回调嵌套:

const { promisify } = require('util');
const jwtVerify = promisify(jwt.verify);

2. 调整Redis操作逻辑,给旧token留缓冲时间

不要立即删除旧refreshToken,而是给它设置短暂过期时间(比如5秒),让Strict Mode的两次请求都能正常验证:

// 替换原del操作,改为设置5秒后自动删除
await redisClient.expire(refreshToken, 5);

如果需要确保旧token无法再次使用,也可以先标记为失效再设置过期:

await redisClient.set(refreshToken, 'invalid', 'EX', 5);

3. 修改后的完整中间件代码

const { promisify } = require('util');
const jwtVerify = promisify(jwt.verify);

const authRefresh = async (req, res) => {
    try {
        const refreshToken = req.cookies['refresh-token'];
        const accessToken = req.cookies['access-token'];

        if (!refreshToken) {
            return res.status(401).json({ status: 'error', message: "Refresh token not found" });
        }

        if (!accessToken || accessToken.includes('mockup')) {
            try {
                const userProperties = await jwtVerify(refreshToken, process.env.JWT_REFRESH);
                const redisValue = await redisClient.get(refreshToken);

                if (redisValue && redisValue !== 'invalid') {
                    const tokens = await generateToken(userProperties);

                    // 设置新的HttpOnly Cookie
                    res.cookie('access-token', tokens.accessToken, cookiesTime.access);
                    res.cookie('refresh-token', tokens.refreshToken, cookiesTime.refresh);

                    // 给旧token设置5秒过期,而非立即删除
                    await redisClient.expire(refreshToken, 5);
                    // 保存新token到Redis
                    await redisClient.set(tokens.refreshToken, tokens.refreshToken, 'EX', time_refresh);

                    return res.status(200).json('Tokens set again');
                } else {
                    return res.status(401).json('No refresh token in db');
                }
            } catch (err) {
                return res.status(401).json(err);
            }
        } else {
            return res.status(202).json('No need to refresh an access token');
        }
    } catch (err) {
        return res.status(401).json({ status: 'error', message: err.message });
    }
};

4. 开发环境临时规避(可选)

如果只是想临时跳过Strict Mode的重复请求,可以暂时移除React应用入口处的<StrictMode>标签,但不建议长期依赖此方案——生产环境不会有这个问题,优化后端逻辑才是根本解决办法。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 08:59:16