You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 21:33:15