Express.js集成CSRF出现ForbiddenError: invalid csrf token问题求助
Express.js CSRF防护无效问题排查与修复
问题背景
在Express.js项目中集成CSRF防护后,登录操作触发ForbiddenError: invalid csrf token错误,配置后仍无法正常通过验证,相关代码如下(已整理):
排查出的核心问题
- EJS模板嵌套form标签:login.ejs中外层form嵌套了内层form,浏览器对嵌套form的解析存在问题,导致提交时仅内层form的字段被发送,用户名、密码和CSRF token无法同时被正确传递。
- Session配置键名错误:App.js中session配置使用了
tasks键名,正确的存储配置键应为store,导致Session未正确持久化到MongoDB,CSRF token依赖Session存储,因此验证失败。 - 手动CSRF验证逻辑错误:UserController.js的postLoginUser中手动调用
req.csrfToken()与提交的token对比,但该方法每次调用都会生成新的token,永远无法匹配,且csurf中间件已自动完成验证,无需手动重复验证。 - 中间件顺序错误:body-parser中间件放在了csrf防护之后,导致csrf中间件无法读取请求体中的token参数。
修复方案及代码修正
1. 修复login.ejs的嵌套form问题
将内层form标签移除,把CSRF token隐藏域和提交按钮移到外层form内:
<!-- 修正后的login.ejs表单部分 --> <h2 class="title">Log in</h2> <form action="/login-user" method="post"> <!--Username --> <div class="row row-space"> <div class="col-2"> <div class="input-group"> <label class="label">Username</label> <input class="input--style-4" type="text" name="username"> </div> </div> </div> <!--Password --> <div class="row row-space"> <div class="col-2"> <div class="input-group"> <label class="label">Password</label> <input class="input--style-4" type="password" name="password"> </div> </div> </div> <!-- Login button press--> <div class="p-t-15"> <div> <input type="hidden" name="_csrf" value="<%= csrfToken %>"> <button class="login-btn btn--radius-2 btn--blue" type="submit">Log in</button> </div> </div> </form>
2. 修正App.js的Session配置
将session配置中的tasks改为store:
// App.js中session配置修正 app.use(session({ secret: 'my secret', resave: false, saveUninitialized: false, store : tasks // 替换原有的tasks键名 }))
3. 调整中间件顺序并移除手动验证逻辑
- 将body-parser移到session和csrf中间件之前;
- 移除UserController.js中手动验证CSRF token的代码块:
修正后的App.js中间件顺序
// App.js中间件顺序调整 app.use(bodyParser.urlencoded({ extended: false })) // 移到最前面 app.use(express.static(path.join(__dirname, '..', '..', 'public'))); app.use(session({ secret: 'my secret', resave: false, saveUninitialized: false, store : tasks })) app.use(csrfProtection);
修正后的postLoginUser方法
// UserController.js的postLoginUser修正 exports.postLoginUser = (req, res, next) => { const username = req.body.username; const password = req.body.password; // 移除手动验证csrf token的代码块 User.findOne({username: username}) .then((user) => { if(!user){ console.log('User field is empty'); res.redirect('/login'); } bcrypt.compare(password, user.password) .then(passwordMatch => { if(passwordMatch){ req.session.isLoggedin = true; req.session.user = user console.log('Password matched saving session and redirecting...') return req.session.save((err) => { if(err){ console.log('req.session.save error: ',err); }else{ res.redirect('/'); } }) }else{ console.log('Password is not correct redirecting back to login page.') res.redirect('/login'); } }) }).catch(err => { console.log('findOne error is: ', err); }) }
验证修复效果
完成上述修改后,重启服务,登录时CSRF token将被正确传递并验证,可正常完成登录流程。
内容的提问来源于stack exchange,提问作者Nansi
相关产品推荐
相关产品推荐

