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

Node.js中JWT过期问题:刷新过期令牌返回jwt expired错误

解决JWT身份验证中令牌过期的区分处理问题

你的核心问题在于没有区分JWT的错误类型,不管是令牌过期还是无效签名等其他错误,都统一返回403状态码和相同提示,导致前端无法区分处理逻辑。同时,当前的refresh token机制缺少数据库校验和自动续期的优化,以下是具体解决方案:

1. 区分JWT错误类型,细化返回信息

修改authenticateToken中间件,针对access token的过期和其他验证错误返回不同状态码和提示:

const authenticateToken = (req, res, next) => {
    const authHeader = req.headers['authorization'];
    const token = authHeader && authHeader.split(' ')[1];

    if (token == null) return res.sendStatus(401);

    jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
        if (err) {
            // 单独处理token过期的情况
            if (err.name === 'TokenExpiredError') {
                return res.status(401).json({ message: 'access token已过期,请刷新令牌' });
            }
            // 其他验证错误(如无效签名、篡改)返回403
            return res.status(403).json({ message: '无效的access token', error: err.message });
        }
        req.user = user;
        next();
    });
};

2. 优化refresh token的验证逻辑

修改/token接口,不仅区分refresh token的过期错误,还增加数据库校验防止token复用,同时支持自动生成新的refresh token续期:

app.post('/token', (req, res) => {
    const { token } = req.body;
    if (!token) return res.sendStatus(401);

    jwt.verify(token, process.env.REFRESH_TOKEN_SECRET, (err, user) => {
        if (err) {
            if (err.name === 'TokenExpiredError') {
                // refresh token过期,强制用户重新登录
                return res.status(401).json({ message: 'refresh token已过期,请重新登录' });
            }
            return res.status(403).json({ message: '无效的refresh token', error: err.message });
        }

        // 校验数据库中存储的refresh token是否与提交的一致,防止被盗用后复用
        db.query('SELECT refresh_token FROM tokens WHERE id_user = ?', [user.id], (queryErr, results) => {
            if (queryErr) throw queryErr;
            if (results.length === 0 || results[0].refresh_token !== token) {
                return res.status(403).json({ message: 'refresh token已失效,请重新登录' });
            }

            // 生成新的access token
            const accessToken = generateAccessToken({ id: user.id, email: user.email });
            // 生成新的refresh token并更新数据库,延长会话有效期
            const newRefreshToken = generateRefreshToken({ id: user.id, email: user.email });
            
            db.query('UPDATE tokens SET refresh_token = ? WHERE id_user = ?', [newRefreshToken, user.id], (updateErr) => {
                if (updateErr) throw updateErr;
                res.json({ accessToken, refreshToken: newRefreshToken });
            });
        });
    });
});

3. 修复登录时的token存储逻辑

原代码中使用UPDATE语句存储refresh token,若用户是首次登录,tokens表中无对应记录会导致存储失败,改为INSERT ... ON DUPLICATE KEY UPDATE:

// 替换login接口中的UPDATE语句
db.query(
    'INSERT INTO tokens (id_user, refresh_token) VALUES (?, ?) ON DUPLICATE KEY UPDATE refresh_token = ?',
    [user.id, refreshToken, refreshToken],
    (err, result) => {
        if (err) throw err;
        res.status(200).json({ result: true, accessToken, refreshToken });
    }
);

关键优化点说明

  • 错误类型区分:通过err.name判断是TokenExpiredError(令牌过期)还是其他错误,让前端能明确执行对应逻辑(access过期调用刷新接口,refresh过期跳转登录页)
  • 数据库校验refresh token:确保只有当前有效的refresh token才能刷新access token,避免旧token被盗用后非法获取新令牌
  • refresh token自动续期:每次刷新access token时生成新的refresh token,既延长用户会话时间,又降低旧token被盗用的风险

内容的提问来源于stack exchange,提问作者joisberg

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 05:25:56