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
相关产品推荐
相关产品推荐

