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

Node.js中Access/Refresh Token实现合理性及安全优化咨询

关于Access/Refresh Token机制的实现分析与优化方案

一、当前实现的合理性分析

你的基础逻辑方向是对的:优先校验Access Token,失效后用Refresh Token刷新双Token,这个流程符合标准的Token刷新模式。但存在几个明显的问题:

  • 回调地狱问题:嵌套两层jwt.verify回调,代码可读性和维护性差,后续扩展逻辑会更混乱。
  • 无状态Refresh Token的风险:JWT本身是无状态的,你当前只验证Refresh Token的签名和有效期,没有在服务器端记录Token的状态。如果Refresh Token被盗,攻击者可以一直用它刷新新的Token,直到30天过期。
  • Cookie安全属性缺失:代码里没体现设置Cookie的HttpOnly、Secure、SameSite属性,这会导致XSS或CSRF攻击风险——比如XSS脚本可以窃取非HttpOnly的Cookie,CSRF攻击可以冒用用户的Cookie发起请求。

二、Refresh Token被盗后的机制风险

这种情况下机制不是冗余,而是存在严重安全漏洞:被盗的Refresh Token在有效期内(30天)可以无限次用来获取新的Access Token和Refresh Token,攻击者能长期冒充用户身份。你的当前实现没有任何手段提前失效被盗的Refresh Token,这是很大的安全隐患。

三、Refresh Token的存储位置选择

Cookie是最优选择,但必须配置安全属性:

  • 必须设置HttpOnly: true:禁止前端JS读取Cookie,从根源上防止XSS窃取。
  • 必须设置Secure: true:仅在HTTPS连接下才发送Cookie,避免明文传输被窃听。
  • 设置SameSite: 'Strict'或'Lax':限制Cookie跨域发送,防范CSRF攻击。
  • 绝对不要存在localStorage或sessionStorage:这些存储可以被前端JS直接读取,XSS脚本轻易就能窃取Token。

四、兼顾便捷性与安全性的正确方案

1. 服务器端存储Refresh Token的哈希值

  • 登录生成Refresh Token后,不要直接存储明文,而是存储它的哈希值(比如用bcrypt哈希)到数据库,关联用户ID、过期时间、设备标识(可选)。
  • 刷新Token时,先验证Refresh Token的签名和有效期,再去数据库查询对应的哈希值是否存在且有效。
  • 刷新成功后,删除旧的Refresh Token哈希,存储新的Refresh Token哈希——这样旧的Refresh Token立刻失效,即使被盗也无法再使用。

2. 优化Token刷新逻辑

改用async/await重构验证代码,避免回调地狱:

exports.verifyAccessToken = (redirect) => async (req, res, next) => {
    const accessToken = req.cookies.accessToken;
    const refreshToken = req.cookies.refreshToken;
    log.debug("verifying, cookies:", req.cookies);

    if (!accessToken) {
        return redirect ? res.redirect("/login") : res.status(401).json({ message: 'Verification Error: Access Token missing' });
    }

    try {
        const decoded = jwt.verify(accessToken, process.env.ACCESS_TOKEN_SECRET);
        req.user = decoded;
        return next();
    } catch (accessErr) {
        if (!refreshToken) {
            return redirect ? res.redirect("/login") : res.status(400).json({ message: 'Verification Error: Access Token invalid/expired, Refresh Token missing' });
        }

        try {
            const decodedRefresh = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET);
            // 从数据库查询该Refresh Token的哈希是否存在且有效
            const storedSession = await db.query('SELECT refresh_hash FROM user_sessions WHERE user_id = ? AND expires_at > NOW()', [decodedRefresh.userId]);
            
            if (!storedSession.length || !bcrypt.compareSync(refreshToken, storedSession[0].refresh_hash)) {
                return redirect ? res.redirect("/login") : res.status(403).json({ message: 'Verification Error: Refresh Token invalid/revoked' });
            }

            // 生成新的双Token
            const newAccessToken = helper.generateNewAccessToken(decodedRefresh.email);
            const newRefreshToken = helper.generateNewRefreshToken(decodedRefresh.email);
            // 存储新的Refresh Token哈希,删除旧的
            await db.query('DELETE FROM user_sessions WHERE user_id = ?', [decodedRefresh.userId]);
            await db.query('INSERT INTO user_sessions (user_id, refresh_hash, expires_at) VALUES (?, ?, ?)', [
                decodedRefresh.userId,
                bcrypt.hashSync(newRefreshToken, 10),
                new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)
            ]);

            // 设置新的Cookie,添加安全属性
            res.cookie('accessToken', newAccessToken, {
                httpOnly: true,
                secure: process.env.NODE_ENV === 'production',
                sameSite: 'Strict',
                maxAge: 60 * 60 * 1000 // 1小时
            });
            res.cookie('refreshToken', newRefreshToken, {
                httpOnly: true,
                secure: process.env.NODE_ENV === 'production',
                sameSite: 'Strict',
                maxAge: 30 * 24 * 60 * 60 * 1000 // 30天
            });

            req.user = decodedRefresh;
            next();
        } catch (refreshErr) {
            return redirect ? res.redirect("/login") : res.status(403).json({ message: 'Verification Error: Refresh Token invalid/expired' });
        }
    }
};

3. 其他安全优化措施

  • 缩短Refresh Token有效期:比如从30天缩短到7天,同时提供“记住我”选项,用户勾选后可以延长到30天——平衡安全性和便捷性。
  • 设备绑定:生成Token时记录用户的设备信息(比如User-Agent哈希),刷新时校验设备信息,避免跨设备盗用。
  • 主动失效机制:提供登出接口,删除数据库中对应的Refresh Token哈希;用户修改密码、账号异常时,批量失效该用户的所有Refresh Token。
  • 敏感操作二次验证:比如修改密码、支付等操作,强制要求用户输入密码或验证码,即使Token有效也需要额外验证。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 15:13:17