基于MVC架构的Refresh Token实现咨询(附现有JWT代码)
MVC架构下实现JWT双Token(Access/Refresh)方案
1. 环境变量配置(.env)
首先在.env中配置双Token的密钥与过期时间:
JWT_ACCESS_SECRET=your_secure_access_key_here JWT_REFRESH_SECRET=your_secure_refresh_key_here JWT_ACCESS_EXPIRES_IN=1m JWT_REFRESH_EXPIRES_IN=7d
2. 模型层:存储Refresh Token
在用户模型中新增refreshToken字段,用于持久化存储有效的Refresh Token(以MongoDB为例):
// models/User.js const mongoose = require('mongoose'); const userSchema = new mongoose.Schema({ username: { type: String, required: true, unique: true }, password: { type: String, required: true }, refreshToken: { type: String, default: null } // 新增字段 }); module.exports = mongoose.model('User', userSchema);
3. JWT中间件封装(jwtMiddleware.js)
封装Token生成、验证逻辑,包含Access Token鉴权中间件和Refresh Token换AccessToken的逻辑:
// middleware/jwtMiddleware.js const jwt = require('jsonwebtoken'); const User = require('../models/User'); // 生成Access Token(短时效) const generateAccessToken = (user) => { return jwt.sign( { id: user._id, username: user.username }, process.env.JWT_ACCESS_SECRET, { expiresIn: process.env.JWT_ACCESS_EXPIRES_IN } ); }; // 生成Refresh Token(长时效) const generateRefreshToken = (user) => { return jwt.sign( { id: user._id }, process.env.JWT_REFRESH_SECRET, { expiresIn: process.env.JWT_REFRESH_EXPIRES_IN } ); }; // Access Token鉴权中间件 const authenticateToken = (req, res, next) => { const authHeader = req.headers['authorization']; const token = authHeader?.split(' ')[1]; if (!token) return res.status(401).json({ message: '缺少访问令牌' }); jwt.verify(token, process.env.JWT_ACCESS_SECRET, (err, user) => { if (err) return res.status(403).json({ message: '访问令牌无效/过期' }); req.user = user; next(); }); }; // Refresh Token换全新Access Token const refreshAccessToken = async (req, res) => { const { refreshToken } = req.body; if (!refreshToken) return res.status(401).json({ message: '缺少刷新令牌' }); // 先验证数据库中是否存在该Refresh Token const user = await User.findOne({ refreshToken }); if (!user) return res.status(403).json({ message: '刷新令牌无效' }); jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET, (err, decoded) => { if (err || decoded.id !== user._id.toString()) { return res.status(403).json({ message: '刷新令牌过期或篡改' }); } // 生成新的Access Token返回 const newAccessToken = generateAccessToken(user); res.json({ accessToken: newAccessToken }); }); }; module.exports = { generateAccessToken, generateRefreshToken, authenticateToken, refreshAccessToken };
4. 控制器层:登录/登出逻辑
在认证控制器中处理登录时的双Token生成、登出时的Refresh Token失效:
// controllers/authController.js const bcrypt = require('bcryptjs'); const User = require('../models/User'); const { generateAccessToken, generateRefreshToken } = require('../middleware/jwtMiddleware'); // 登录接口 const login = async (req, res) => { const { username, password } = req.body; // 验证用户存在 const user = await User.findOne({ username }); if (!user) return res.status(400).json({ message: '用户不存在' }); // 验证密码 const isValidPwd = await bcrypt.compare(password, user.password); if (!isValidPwd) return res.status(400).json({ message: '密码错误' }); // 生成双Token const accessToken = generateAccessToken(user); const refreshToken = generateRefreshToken(user); // 将Refresh Token存入数据库 user.refreshToken = refreshToken; await user.save(); // 返回Token(生产环境建议把Refresh Token存入HttpOnly Cookie) res.json({ accessToken, refreshToken, user: { id: user._id, username: user.username } }); }; // 登出接口:清除数据库中的Refresh Token const logout = async (req, res) => { const { refreshToken } = req.body; if (refreshToken) { await User.findOneAndUpdate({ refreshToken }, { refreshToken: null }); } res.status(204).send(); }; module.exports = { login, logout };
5. 路由层:配置接口路由
在mainRoutes.js中配置登录、登出、刷新Token及需要鉴权的业务路由:
// routes/mainRoutes.js const express = require('express'); const router = express.Router(); const authController = require('../controllers/authController'); const { authenticateToken, refreshAccessToken } = require('../middleware/jwtMiddleware'); // 公开路由 router.post('/login', authController.login); router.post('/logout', authController.logout); router.post('/refresh-token', refreshAccessToken); // 需要鉴权的路由示例 router.get('/protected-data', authenticateToken, (req, res) => { res.json({ message: '这是受保护的数据', user: req.user }); }); module.exports = router;
关键注意事项
- Refresh Token安全存储:生产环境不要把Refresh Token存在前端localStorage,建议存入
HttpOnly+Secure属性的Cookie,避免XSS攻击。 - 主动失效机制:用户改密码、登出时,及时清除数据库中的Refresh Token,防止被盗用。
- 黑名单优化:如果系统用户量较大,可改用Redis存储失效的Refresh Token黑名单,查询效率更高。
内容的提问来源于stack exchange,提问作者yellowdog
相关产品推荐
相关产品推荐

