乐闻世界logo
搜索文章和话题

JWT 的过期时间如何设置,如何实现刷新机制

2026年2月21日 17:52

JWT 的过期时间管理和刷新机制是保证安全性和用户体验的重要部分。以下是详细的实现方案:

过期时间设置

1. 基本过期时间设置

javascript
const jwt = require('jsonwebtoken'); // 使用 expiresIn 参数 const token = jwt.sign(payload, 'secret', { expiresIn: '1h' // 1小时 // expiresIn: '30m' // 30分钟 // expiresIn: '2d' // 2天 // expiresIn: '7d' // 7天 // expiresIn: 3600 // 3600秒(1小时) }); // 或者使用 exp 声明 const exp = Math.floor(Date.now() / 1000) + (60 * 60); // 1小时后过期 const token = jwt.sign({ ...payload, exp }, 'secret');

2. 推荐的过期时间策略

  • Access Token: 15-30 分钟
  • Refresh Token: 7-30 天
  • Remember Me Token: 30-90 天

Refresh Token 机制实现

1. 双 Token 架构

javascript
const crypto = require('crypto'); const jwt = require('jsonwebtoken'); // 存储 Refresh Token(生产环境使用 Redis) const refreshTokens = new Map(); // 生成 Access Token function generateAccessToken(userId) { return jwt.sign( { userId }, process.env.JWT_SECRET, { expiresIn: '15m', issuer: 'your-app.com', audience: 'your-api' } ); } // 生成 Refresh Token function generateRefreshToken() { return crypto.randomBytes(40).toString('hex'); } // 登录接口 app.post('/auth/login', async (req, res) => { const { username, password } = req.body; // 验证用户凭据 const user = await validateUser(username, password); if (!user) { return res.status(401).json({ error: 'Invalid credentials' }); } // 生成 tokens const accessToken = generateAccessToken(user.id); const refreshToken = generateRefreshToken(); // 存储 refresh token(带过期时间) refreshTokens.set(refreshToken, { userId: user.id, expiresAt: Date.now() + (7 * 24 * 60 * 60 * 1000) // 7天 }); res.json({ accessToken, refreshToken, expiresIn: 900 // 15分钟(秒) }); }); // 刷新 token 接口 app.post('/auth/refresh', (req, res) => { const { refreshToken } = req.body; if (!refreshToken) { return res.status(400).json({ error: 'Refresh token required' }); } const tokenData = refreshTokens.get(refreshToken); if (!tokenData) { return res.status(401).json({ error: 'Invalid refresh token' }); } // 检查是否过期 if (Date.now() > tokenData.expiresAt) { refreshTokens.delete(refreshToken); return res.status(401).json({ error: 'Refresh token expired' }); } // 生成新的 access token const newAccessToken = generateAccessToken(tokenData.userId); res.json({ accessToken: newAccessToken, expiresIn: 900 }); }); // 登出接口 app.post('/auth/logout', (req, res) => { const { refreshToken } = req.body; refreshTokens.delete(refreshToken); res.json({ success: true }); });

2. Redis 存储实现(推荐)

javascript
const redis = require('redis'); const client = redis.createClient(); async function storeRefreshToken(refreshToken, userId, expiresIn = '7d') { await client.setex( `refresh:${refreshToken}`, 60 * 60 * 24 * 7, // 7天(秒) userId ); } async function getUserIdFromRefreshToken(refreshToken) { const userId = await client.get(`refresh:${refreshToken}`); return userId; } async function deleteRefreshToken(refreshToken) { await client.del(`refresh:${refreshToken}`); }

自动刷新机制

1. 前端自动刷新

javascript
// 使用 Axios 拦截器 import axios from 'axios'; const api = axios.create({ baseURL: 'https://api.example.com' }); let isRefreshing = false; let refreshSubscribers = []; function subscribeTokenRefresh(callback) { refreshSubscribers.push(callback); } function onRefreshed(token) { refreshSubscribers.forEach(callback => callback(token)); refreshSubscribers = []; } api.interceptors.request.use(config => { const token = localStorage.getItem('accessToken'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); api.interceptors.response.use( response => response, async error => { const originalRequest = error.config; if (error.response?.status === 401 && !originalRequest._retry) { if (isRefreshing) { return new Promise(resolve => { subscribeTokenRefresh(token => { originalRequest.headers.Authorization = `Bearer ${token}`; resolve(api(originalRequest)); }); }); } originalRequest._retry = true; isRefreshing = true; try { const refreshToken = localStorage.getItem('refreshToken'); const response = await axios.post('/auth/refresh', { refreshToken }); const { accessToken } = response.data; localStorage.setItem('accessToken', accessToken); onRefreshed(accessToken); isRefreshing = false; originalRequest.headers.Authorization = `Bearer ${accessToken}`; return api(originalRequest); } catch (refreshError) { isRefreshing = false; localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); window.location.href = '/login'; return Promise.reject(refreshError); } } return Promise.reject(error); } );

2. 定时刷新

javascript
// 在 token 过期前 5 分钟刷新 function scheduleTokenRefresh() { const token = localStorage.getItem('accessToken'); if (!token) return; const decoded = jwt.decode(token); const expiresIn = decoded.exp * 1000 - Date.now(); const refreshBefore = 5 * 60 * 1000; // 5分钟 if (expiresIn > refreshBefore) { setTimeout(async () => { try { const refreshToken = localStorage.getItem('refreshToken'); const response = await axios.post('/auth/refresh', { refreshToken }); localStorage.setItem('accessToken', response.data.accessToken); scheduleTokenRefresh(); // 继续调度 } catch (error) { console.error('Token refresh failed', error); } }, expiresIn - refreshBefore); } } // 登录成功后启动定时刷新 scheduleTokenRefresh();

高级特性

1. Token 黑名单

javascript
const redis = require('redis'); const client = redis.createClient(); // 登出时将 token 加入黑名单 async function logout(token) { const decoded = jwt.decode(token); const ttl = decoded.exp - Math.floor(Date.now() / 1000); if (ttl > 0) { await client.setex(`blacklist:${token}`, ttl, '1'); } } // 验证时检查黑名单 async function verifyToken(token) { const isBlacklisted = await client.exists(`blacklist:${token}`); if (isBlacklisted) { throw new Error('Token is blacklisted'); } return jwt.verify(token, process.env.JWT_SECRET); }

2. Token 版本控制

javascript
// 在用户表中维护 tokenVersion 字段 function generateAccessToken(userId, tokenVersion) { return jwt.sign( { userId, tokenVersion }, process.env.JWT_SECRET, { expiresIn: '15m' } ); } // 验证时检查版本 async function verifyToken(token) { const decoded = jwt.verify(token, process.env.JWT_SECRET); const user = await getUserById(decoded.userId); if (user.tokenVersion !== decoded.tokenVersion) { throw new Error('Token version mismatch'); } return decoded; } // 强制登出所有设备(增加版本号) async function revokeAllTokens(userId) { await incrementTokenVersion(userId); }

3. 滑动过期时间

javascript
// 每次使用 refresh token 时延长其有效期 app.post('/auth/refresh', async (req, res) => { const { refreshToken } = req.body; const tokenData = await getRefreshTokenData(refreshToken); // 延长 refresh token 有效期 await extendRefreshTokenExpiry(refreshToken, 7 * 24 * 60 * 60); // 7天 const newAccessToken = generateAccessToken(tokenData.userId); res.json({ accessToken: newAccessToken, expiresIn: 900 }); });

最佳实践

  1. 使用短期 Access Token: 15-30 分钟
  2. 使用长期 Refresh Token: 7-30 天
  3. Refresh Token 存储在 HttpOnly Cookie: 防止 XSS
  4. 实现 Token 黑名单: 支持主动撤销
  5. 添加 Token 版本控制: 支持强制登出
  6. 记录 Token 使用日志: 监控异常行为
  7. 限制 Refresh Token 使用次数: 防止滥用
  8. 使用 HTTPS 传输: 防止中间人攻击

通过合理的过期时间管理和刷新机制,可以在安全性和用户体验之间取得良好平衡。

标签:JWT