5月27日 23:08
JWT 的过期时间怎么设?刷新机制怎么实现?
直接回答
过期时间设置:Access Token 设 15-30 分钟,Refresh Token 设 7-30 天。 Access Token 短过期限制盗用窗口,Refresh Token 长过期保证用户体验,两者配合实现安全与可用性的平衡。
刷新机制核心思路:双 Token 架构。 登录时同时签发 Access Token(短命)和 Refresh Token(长命),前者用于业务请求,后者仅在 Access Token 过期时调用刷新接口换取新的 Access Token。Refresh Token 必须存服务端(推荐 Redis),支持主动撤销。
过期时间设置
javascriptconst jwt = require('jsonwebtoken'); // Access Token:15 分钟 const accessToken = jwt.sign({ userId }, SECRET, { expiresIn: '15m' }); // Refresh Token:7 天(不使用 JWT,用随机字符串存 Redis) const refreshToken = crypto.randomBytes(40).toString('hex'); await redis.setex(`refresh:${refreshToken}`, 7 * 24 * 3600, userId);
为什么不推荐 Refresh Token 也用 JWT? JWT 一旦签发无法撤销,如果 Refresh Token 也是 JWT 则无法实现服务端主动失效,失去 Refresh Token 存在的意义。
刷新接口实现
javascriptapp.post('/auth/refresh', async (req, res) => { const { refreshToken } = req.body; const userId = await redis.get(`refresh:${refreshToken}`); if (!userId) { return res.status(401).json({ error: 'Invalid or expired refresh token' }); } // 生成新 Access Token const accessToken = jwt.sign({ userId }, SECRET, { expiresIn: '15m' }); // 可选:轮换 Refresh Token(刷新时同时换发新的,旧的作废) await redis.del(`refresh:${refreshToken}`); const newRefresh = crypto.randomBytes(40).toString('hex'); await redis.setex(`refresh:${newRefresh}`, 7 * 24 * 3600, userId); res.json({ accessToken, refreshToken: newRefresh, expiresIn: 900 }); });
前端自动刷新
核心思路:Axios 响应拦截器捕获 401,自动用 Refresh Token 换新 Access Token,然后重试原请求。并发场景下用队列避免多次刷新。
javascriptapi.interceptors.response.use( response => response, async error => { const originalRequest = error.config; if (error.response?.status === 401 && !originalRequest._retry) { originalRequest._retry = true; const { refreshToken } = localStorage; const { data } = await axios.post('/auth/refresh', { refreshToken }); localStorage.accessToken = data.accessToken; localStorage.refreshToken = data.refreshToken; originalRequest.headers.Authorization = `Bearer ${data.accessToken}`; return api(originalRequest); } return Promise.reject(error); } );
追问:Refresh Token 被盗怎么办?
- Token 轮换:每次刷新时换发新 Refresh Token、作废旧的,被盗的旧 Token 下次使用时会被服务端拒绝
- Token 版本控制:用户表加
tokenVersion字段写入 Access Token,修改密码或强制登出时递增版本号,旧 Token 自动失效 - 使用次数限制:同一个 Refresh Token 只允许刷新一次,重复使用说明可能被盗,立刻撤销该用户所有 Refresh Token
追问:如何强制用户下线?
在用户表维护 tokenVersion,签发 Access Token 时写入该值。验证时对比数据库中的版本号,不一致则拒绝。强制下线只需递增版本号,所有旧 Token 立即失效,无需等过期。
javascript// 签发时 const accessToken = jwt.sign({ userId, tokenVersion: user.tokenVersion }, SECRET, { expiresIn: '15m' }); // 验证时 const decoded = jwt.verify(token, SECRET); const user = await getUserById(decoded.userId); if (user.tokenVersion !== decoded.tokenVersion) { throw new Error('Token revoked'); }
追问:并发刷新会不会产生竞态?
会。多个请求同时 401 时可能触发多次刷新。解法:前端用 isRefreshing 标志 + 订阅队列,第一个请求执行刷新,后续请求排队等刷新完成后拿到新 Token 重试。
关键要点
- Access Token 短过期(15-30m),Refresh Token 长过期(7-30d),后者存 Redis
- Refresh Token 不用 JWT,用随机字符串,保证可撤销
- 刷新时轮换 Refresh Token,防盗用
- 强制下线用 tokenVersion 方案,最简洁
- 前端用拦截器 + 队列解决自动刷新和并发竞态