# 用 Cookie+HttpOnly 实现双 Token 认证与滑动刷新
# 为什么需要双 Token 方案?
传统单 Token 方案面临两大痛点:
- 安全风险 - Token 泄露等于永久入侵
- 体验问题 - 频繁重新登录影响用户体验
双 Token 方案完美解决:
- 🛡️ Access Token(短时效):15-30 分钟有效期,降低被盗风险
- 🔄 Refresh Token(长时效):7-30 天有效期,静默刷新会话
# 技术选型
组件 | 方案 | 优势说明 |
---|
Access Token | JWT + localStorage | 方便前端使用,自动过期 |
Refresh Token | HttpOnly Cookie | 防止 XSS 窃取,服务端可控 |
加密算法 | bcrypt | |
# 实现核心流程
🔑 认证流程全解析(→ 表示请求,← 表示响应)
1. 【登录阶段】
用户 → 前端:提交账号密码
前端 → 后端:POST /login
后端 ← 前端:
│ HTTP Status 200
├─ Headers: Set-Cookie: refreshToken=xxx; HttpOnly
└─ Body: { "accessToken": "jwt_token" }
2. 【正常请求】
用户 → 前端:发起数据请求
前端 → 后端:
│ Headers: Authorization: Bearer accessToken
├─ 请求敏感数据
后端 ← 前端:
│ HTTP Status 401(Token过期)
└─ Body: { "error": "Token expired" }
3. 【自动刷新】
前端 → 后端:POST /refresh-token
│ 自动携带 Cookie: refreshToken=xxx
后端 ← 前端:
│ HTTP Status 200
└─ Body: { "accessToken": "new_jwt_token" }
4. 【重试请求】
前端 → 后端:重新发送原始请求
│ Headers: Authorization: Bearer new_jwt_token
后端 ← 前端:
│ HTTP Status 200
└─ Body: 请求数据
# 核心代码实现
# 后端关键代码
| |
| router.post('/login', async (req, res) => { |
| |
| |
| |
| const accessToken = generateAccessToken(user); |
| const refreshToken = generateRefreshToken(user); |
| |
| |
| res.cookie('refreshToken', refreshToken, { |
| httpOnly: true, |
| secure: process.env.NODE_ENV === 'production', |
| sameSite: 'lax', |
| maxAge: 7 * 24 * 60 * 60 * 1000 |
| }); |
| |
| res.json({ accessToken }); |
| }); |
| |
| |
| router.post('/refresh', (req, res) => { |
| const refreshToken = req.cookies.refreshToken; |
| |
| try { |
| const decoded = verifyRefreshToken(refreshToken); |
| const newAccessToken = generateAccessToken(decoded); |
| res.json({ accessToken: newAccessToken }); |
| } catch (error) { |
| res.clearCookie('refreshToken'); |
| res.status(401).end(); |
| } |
| }); |
# 前端拦截器
| |
| const api = axios.create({ |
| baseURL: API_URL, |
| withCredentials: true |
| }); |
| |
| |
| api.interceptors.response.use(response => response, async error => { |
| const originalRequest = error.config; |
| |
| if (error.response.status === 401 && !originalRequest._retry) { |
| originalRequest._retry = true; |
| |
| try { |
| const { data } = await api.post('/refresh'); |
| localStorage.setItem('accessToken', data.accessToken); |
| return api(originalRequest); |
| } catch (e) { |
| logout(); |
| } |
| } |
| |
| return Promise.reject(error); |
| }); |
# 避坑指南🚨
- CORS 配置:未启用 CORS 凭证
| |
| app.use(cors({ |
| origin: 'http://localhost:3000', |
| credentials: true |
| })); |
- Cookie 参数:Cookie 参数配置错误
| res.cookie('refreshToken', token, { |
| httpOnly: true, |
| secure: process.env.NODE_ENV === 'production', |
| sameSite: process.env.NODE_ENV === 'production' ? 'none' : 'lax' |
| }); |
- 缺少 cookie-parser 中间件
| npm install cookie-parser |
| const cookieParser = require('cookie-parser'); |
| app.use(cookieParser()); |
- 前端请求头:前端请求头配置错误
# 滑动刷新 refreshToken 实现
什么是滑动刷新机制?
滑动刷新机制是指在用户执行某些关键操作(如进入房间、提交表单等)时,服务器会检查并更新 refreshToken 的有效期。这样可以确保用户在一段时间内保持活跃状态,而不会因为 refreshToken 过期而被迫重新登录。
- 从请求中获取 refreshToken
| const refreshTokenFromCookie = req.cookies.refreshToken; |
| if (!refreshTokenFromCookie) { |
| return res.status(400).json({ error: '刷新令牌缺失!' }); |
| } |
- 验证 refreshToken 是否有效
| try { |
| const decoded = verifyRefreshToken(refreshTokenFromCookie); |
| if (!decoded) { |
| return res.status(401).json({ error: '无效的刷新令牌!' }); |
| } |
| } catch (error) { |
| console.error(error); |
| return res.status(401).json({ error: '验证刷新令牌失败!' }); |
| } |
- 生成新的 refreshToken 并设置到 Cookie
这里的 config.jwtRefreshTokenExpiry
是配置文件中的刷新令牌有效期,单位为秒。
| const payload = { userId: decoded.userId, nickname: decoded.nickname }; |
| const newRefreshToken = generateRefreshToken(payload); |
| |
| const jwtExpiryInSeconds = parseInt(config.jwtRefreshTokenExpiry); |
| const cookieMaxAge = jwtExpiryInSeconds * 1000; |
| |
| res.cookie('refreshToken', newRefreshToken, { |
| httpOnly: true, |
| secure: process.env.NODE_ENV === 'production', |
| sameSite: process.env.NODE_ENV === 'production' ? 'none' : 'lax', |
| domain: process.env.NODE_ENV === 'production' ? '.yourdomain.com' : 'localhost', |
| path: '/', |
| maxAge: cookieMaxAge, |
| }); |
# 总结
通过 HttpOnly Cookie + JWT 的双 Token 方案,我们实现了:
- ✅ 更高的安全性(防 XSS 窃取)
- ✅ 无缝的用户体验(静默刷新)
- ✅ 灵活的控制能力(强制下线、设备管理)
完整代码 https://github.com/aynya/VoiceChat