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

Node.js中CSRF令牌使用后未失效的问题求助

问题分析与解决方案

你的问题核心在于当前实现没有对CSRF令牌的单次有效性做校验,同时使用了全局固定的secret,导致只要令牌能通过secret验证,不管是否用过都能提交成功。

代码中的问题点

  1. 全局共享secret:你初始化了一个全局的csrfToken(实际是csrf库的secret值),所有请求都用这个secret生成和验证令牌。csrf库的verify方法只校验令牌是否由该secret生成,不会检查令牌是否已被使用过。
  2. 无令牌失效机制:没有维护已使用令牌的记录,无法判断当前提交的令牌是否是第一次使用。

修复方案

要实现令牌单次使用即失效,需要:

  • 为每个用户会话维护独立的secret(而非全局共享)
  • 记录每个会话已使用过的令牌,验证时同时检查令牌的有效性和未使用状态

以下是修改后的代码示例(结合express-session实现会话存储):

1. 依赖安装与基础配置

npm install express-session csrf

2. 主应用代码修改

import csrf from 'csrf';
import express from 'express';
import session from 'express-session';

const App = express();

// 配置session,用于存储每个用户的csrf secret和已使用令牌
App.use(session({
  secret: 'your-session-secret',
  resave: false,
  saveUninitialized: true,
  cookie: { secure: process.env.NODE_ENV === 'production' }
}));

const tokens = new csrf({ cookie: true });

App.use(express.urlencoded({ extended: true }));
App.use(express.json());

App.use((req, res, next) => {
  // 为当前会话初始化csrf secret和已使用令牌列表
  if (!req.session.csrfSecret) {
    req.session.csrfSecret = tokens.secretSync({ saltLength: 128 });
    req.session.usedCsrfTokens = [];
  }

  // 非GET/OPTIONS请求验证csrf令牌
  if (!['get', 'options'].includes(req.method.toLowerCase())) {
    const submittedToken = req.body._csrf || req.headers['x-csrf-token'];
    
    // 1. 验证令牌是否有效(由当前会话的secret生成)
    const isTokenValid = tokens.verify(req.session.csrfSecret, submittedToken);
    // 2. 验证令牌是否未被使用过
    const isTokenUnused = !req.session.usedCsrfTokens.includes(submittedToken);

    if (!isTokenValid || !isTokenUnused) {
      return res.status(403).json({ error: 'CSRF: Invalid or reused token' });
    }

    // 标记令牌为已使用
    req.session.usedCsrfTokens.push(submittedToken);
  }

  // 生成新的csrf令牌供后续请求使用
  req.csrfToken = tokens.create(req.session.csrfSecret);

  next();
});

// 引入controller
import { csrfToken } from './controller.js';
App.get('/csrf-token', csrfToken);

// 其他路由示例
App.post('/submit', (req, res) => {
  res.json({ success: true });
});

App.listen(3000);

3. controller.js 无需修改

const csrfToken = async (req, res) => res.json({
    csrf: req.csrfToken
});

export {
    csrfToken,
};

关键改进点

  • 每个用户会话拥有独立的csrf secret,避免全局共享带来的安全风险
  • 新增usedCsrfTokens数组记录已使用的令牌,验证时双重校验(有效性+未使用)
  • 验证通过后立即将令牌加入已使用列表,禁止后续复用

附请求发送截图及应用请求日志截图:
发送请求截图
应用请求日志截图

内容的提问来源于stack exchange,提问作者Mr.Singh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 06:30:29