基于Redis的JWT静默刷新问题(MERN栈)
问题根源
React Strict Mode在开发环境会重复触发组件副作用(比如API请求),导致刷新接口被连续调用两次。第一次请求处理时,你执行了await redisClient.del(redisValue)删除旧refreshToken对应的Redis记录;而第二次请求时,客户端还没来得及更新cookie,依然携带旧refreshToken,此时Redis中已无对应条目,所以返回No refresh token in db。
代码问题分析
- JWT验证逻辑混乱:在async函数里混用jwt.verify的回调版本,导致代码嵌套复杂,容易引发异步执行顺序问题。
- 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
相关产品推荐
相关产品推荐

