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

Node.js中Csurf EBADCSRFTOKEN错误:Admin路由CSRF验证失败

问题描述

博客项目中,login、register、logout、更新账户信息等POST路由的CSRF验证功能正常,但在/admin路由下,创建/更新文章或分类时抛出ForbiddenError: invalid csrfToken(错误码:EBADCSRFTOKEN)。

核心代码

require('dotenv').config();

const path = require('path');
const express = require('express');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const session = require('express-session');
const MongoStore = require('connect-mongo');
const flash = require('connect-flash');
const csrf = require('csurf');

const errorController = require('./controllers/error');
const uri = process.env.URI;
const app = express();
const csrfProtection = csrf();

app.set('view engine', 'ejs');
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public')));
app.use(
  session({
    secret: 'my secrets',
    saveUninitialized: false,
    resave: false,
    store: MongoStore.create({
      mongoUrl: uri,
      autoRemove: 'native',
      ttl: 7200, //2 hours
      collectionName: 'sessions',
    }),
  })
);
app.use(csrfProtection);
app.use(flash());

app.use((req, res, next) => {
  res.locals.isAuthenticated = req.session.isLoggedIn;
  res.locals.name = undefined;                
  res.locals.level = 0;                       
  if (req.session.user) {                     
    res.locals.name = req.session.user.name;  
    res.locals.level = req.session.user.level;
  }                                           
  res.locals.csrfToken = req.csrfToken();
  next();
});

// routes
const authRoutes = require('./routes/auth');
const pageRoutes = require('./routes/page');
const adminRoutes = require('./routes/admin');

app.use(pageRoutes);            
app.use('/auth', authRoutes);   
app.use('/admin', adminRoutes); 

// error pages
app.use(errorController.get404);

app.use((error, req, res, next) => {
  console.log(error);
  res.status(500).render('errors/500', {
    pageTitle: '500',
    error: error,
  });
});

mongoose
  .connect(uri)
  .then(result => {
    app.listen(3000);
  })
  .catch(error => console.log(error));

表单CSRF字段

所有POST表单已添加:

<input type="hidden" name="_csrf" value="<%= csrfToken%>" />

错误日志

ForbiddenError: invalid csrf token
    at csrf (D:\Tai lieu hoc tap\Code\Personal Projects\Blog\node_modules\csurf\index.js:112:19)
    at Layer.handle [as handle_request] (D:\Tai lieu hoc tap\Code\Personal Projects\Blog\node_modules\express\lib\router\layer.js:95:5)
    at trim_prefix (D:\Tai lieu hoc tap\Code\Personal Projects\Blog\node_modules\express\lib\router\index.js:328:13)
    at D:\Tai lieu hoc tap\Code\Personal Projects\Blog\node_modules\express\lib\router\index.js:286:9
    at Function.process_params (D:\Tai lieu hoc tap\Code\Personal Projects\Blog\node_modules\express\lib\router\index.js:346:12)      
    at next (D:\Tai lieu hoc tap\Code\Personal Projects\Blog\node_modules\express\lib\router\index.js:280:10)
    at D:\Tai lieu hoc tap\Code\Personal Projects\Blog\node_modules\express-session\index.js:506:7
    at D:\Tai lieu hoc tap\Code\Personal Projects\Blog\node_modules\connect-mongo\build\main\lib\MongoStore.js:221:17
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  code: 'EBADCSRFTOKEN'
}

解决方案

1. 验证Admin模板中CSRF Token的渲染

打开浏览器开发者工具,查看/admin路由下表单的隐藏_csrf字段:

  • 确认字段值不为空,且和页面渲染时生成的token一致(可在模板中临时添加<%= csrfToken %>到可见区域,对比提交值);
  • 检查模板中变量名是否拼写正确,避免csrf_token这类错误写法。

2. 检查Admin路由的Session可用性

CSRF验证依赖session,在admin路由的处理函数中添加日志:

router.post('/create-post', (req, res, next) => {
  console.log('当前Session:', req.session);
  console.log('请求携带的CSRF Token:', req.body._csrf);
  console.log('预期的CSRF Token:', req.csrfToken());
  next();
});

如果session未正常获取(比如已登录但req.session.user不存在):

  • 检查MongoDB的sessions集合,确认有对应用户的session文档;
  • 确认浏览器Cookie设置未限制/admin路径的session传递(比如SameSite配置)。

3. 确认CSRF中间件作用范围

检查adminRoutes是否存在自行绕过CSRF验证的代码,比如:

// 错误示例:跳过POST请求的CSRF验证
router.use((req, res, next) => {
  if (req.method === 'POST') {
    next();
  } else {
    csrfProtection(req, res, next);
  }
});

确保/admin路由完全继承全局的csrf中间件。

4. 切换为Cookie模式存储CSRF Token(备选方案)

将CSRF配置改为Cookie存储,避免session模式的潜在问题:

// 修改CSRF初始化
const csrfProtection = csrf({ cookie: true });

// 同步更新session的cookie配置
app.use(
  session({
    secret: 'my secrets',
    saveUninitialized: false,
    resave: false,
    store: MongoStore.create({
      mongoUrl: uri,
      autoRemove: 'native',
      ttl: 7200,
      collectionName: 'sessions',
    }),
    cookie: {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production', // 生产环境开启
      sameSite: 'lax',
    }
  })
);

表单中的CSRF字段无需修改,req.csrfToken()会自动从Cookie生成对应值。

5. 检查Body Parser的兼容性

在admin路由的POST处理函数中打印请求体:

router.post('/create-category', (req, res) => {
  console.log('解析后的请求体:', req.body);
  // ...后续逻辑
});

确认req.body._csrf存在且值正确,若不存在则检查表单提交的编码方式是否与body-parser.urlencoded兼容。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 04:34:53