Node.js 在express中提交表单时CSRF令牌不起作用

Node.js 在express中提交表单时CSRF令牌不起作用,node.js,express,Node.js,Express,我正在尝试让表单在我的express应用程序中工作。我有一个中间件函数,它将csrf令牌req.session.\u csrf传递给res.locals.csrf\u令牌,以便视图可以使用它。现在,我试图在视图中使用局部变量,并且从会话中间件中得到一个禁止的错误 这是我的表单代码-我使用把手作为模板引擎: <form method='post' action='/api/entries' enctype='multipart/form-data' > <input

我正在尝试让表单在我的express应用程序中工作。我有一个中间件函数,它将csrf令牌req.session.\u csrf传递给res.locals.csrf\u令牌,以便视图可以使用它。现在,我试图在视图中使用局部变量,并且从会话中间件中得到一个禁止的错误

这是我的表单代码-我使用把手作为模板引擎:

  <form method='post' action='/api/entries' enctype='multipart/form-data' >
    <input type='hidden' name='_csrf' value={{csrf_token}} />
    <input class='foo' type='text' />
    <input class='bar' type='text' />
    <button id='submit' type='submit'> SUBMIT
  </form>
在我的应用程序目录中。这是输出。。它看起来不像我在视图之外的任何地方引用它,在视图中,我隐藏的CSRF字段被命名为“_CSRF”

/node\u modules/express/node\u modules/connect/lib/middleware/csrf.js:var secret=req.session.\u csrfSecret;
./node_modules/express/node_modules/connect/lib/middleware/csrf.js:req.session.\u csrfSecret=secret;
./node_modules/express/node_modules/connect/lib/middleware/csrf.js:Object.defineProperty(req.session,“_csrf”{
./node_modules/express/node_modules/connect/lib/middleware/csrf.js:console.warn('req.session.\u csrf已弃用,请改用req.csrfToken());
./node_modules/express/node_modules/connect/lib/middleware/csrf.js:return(req.body&&req.body.\csrf)
./node_modules/express/node_modules/connect/lib/middleware/csrf.js:| | |(req.query和&req.query._csrf)
./v/home.hbs:
./v/show.hbs:
下面是我在尝试发布到/api/entries端点时得到的整个错误堆栈(我之前愚蠢地忽略了提及这一点,但我使用connectredis作为会话中间件):

错误:禁止
在Object.exports.error(appFolder/node_modules/express/node_modules/connect/lib/utils.js:63:13)
在createToken(appFolder/node_modules/express/node_modules/connect/lib/middleware/csrf.js:82:55)
在Object.handle(appFolder/node_modules/express/node_modules/connect/lib/middleware/csrf.js:48:24)
下一步(appFolder/node_modules/express/node_modules/connect/lib/proto.js:193:15)
下一步(appFolder/node_modules/express/node_modules/connect/lib/middleware/session.js:318:9)
在appFolder/node_modules/express/node_modules/connect/lib/middleware/session.js:342:9
在appFolder/node_modules/connect redis/lib/connect redis.js:101:14
在try_回调时(appFolder/node_modules/redis/index.js:580:9)
在RedisClient.return\u reply(appFolder/node\u modules/redis/index.js:670:13)
在ReplyParser(appFolder/node_modules/redis/index.js:312:14)

编辑2:connect-redis.js中的错误是一个函数试图通过会话ID获取当前会话,但失败了。不知道为什么会发生这种情况,我的connect-redis设置看起来是正确的。这让我非常恼火CSRF语法在最新版本的Express/connect中发生了轻微的更改。您现在希望您的中间件像这样:

.use(express.csrf())
.use(function (req, res, next) {
  res.cookie('XSRF-TOKEN', req.csrfToken());
  res.locals.csrftoken = req.csrfToken();
  next();
})

为了测试代码,请注意,您首先需要获取表单页面以生成CSRF令牌。只有这样,您的发布才会成功。如果失败,您需要在再次尝试发布之前在浏览器中重新加载页面。

编辑:如果不需要上传文件,请不要使用
多部分/表单数据
enctype.Swit切换到默认的enctype将允许
express.csrf()
解析
\u csrf
令牌

为了使用
multipart/form data
enctype解析表单,您需要在应用程序配置中使用多部分解析器,或者自己处理文件上载。建议避免使用附带的
express.bodyParser()
并在您希望上传文件的路径上使用类似于
busboy
forgible
的方法,以防止出现错误

如果您这样做,您的
\u csrf
字段将不再被
express.csrf()
捕获,因为只有在请求通过该中间件之后才会解析表单体。请将表单操作设置为
'/api/entries?\u csrf={{csrf\u token}}'
以避免此问题

var fs = require('fs');
var async = require('async');
var express = require('express');
var formidable = require('formidable');
var app = express();

app.use(express.urlencoded())
  .use(express.json())
  .use(express.cookieParser())
  .use(express.session())
  .use(express.csrf())

app.get('/upload', function(req, res) {
  // File uploads ignored.
  res.render('upload', {_csrf:req.csrfToken()});
});

app.post('/upload', function(req, res) {
  // Explicitly handle uploads
  var form = new formidable.IncomingForm();
  form.uploadDir = 'temp';

  var count = 0;
  var maxAllowed = 10;

  form.onPart = function(part) {
    if (!part.filename) return form.handlePart(part);

    count++;

    // Ignore any more files.
    if (count > maxAllowed) return part.resume();

    form.handlePart(part);
  };

  form.parse(req, function(err, fields, files) {
    // Process the files. If you don't need them, delete them.
    // Note that you should still reap your temp directory on occasion.

    async.map(Object.keys(files), function(key, cb) {
      fs.unlink(files[key].path, cb);
    }, function(err) {
      res.end();
    });
   });
});

我今天也遇到了这个问题,花了几个小时才找到解决方案。希望这个答案能帮助别人解决我的问题。作为@amagumori,我正在使用redis进行会话处理,express 3.4.8,连接redis 1.4.7

基本上,我能够确定express配置的顺序会影响新令牌的发行次数。似乎所有公共服务都在创建新令牌

特别是在我的情况下,我必须移动电话

app.use(express.methodOverride());
app.use(express.bodyParser());
app.use(express.static(__dirname + '/public'));
在上面

app.use(express.csrf());
app.use(function(req, res, next){
    res.locals.token = req.csrfToken();
    next();
});

并且会按预期为会话发出令牌。

您如何呈现模板?您正在传递值
csrf_token
?我的express配置中有一个函数,看起来像:app.use(函数(req,res,next){res.locals.csrf_token=req.session.\u csrf;next();})我尝试显式地将其添加到res.render()中包含的局部对象,但没有成功。
render
参数是什么?
{csrf\u token:xyz}
?通过这种方式实现,现在我遇到了一个类型错误:当我尝试使用表单获取页面时,Object没有方法“csrfToken”,因此我使用的是express 3.3.4,请升级到3.4.6,然后再试一次。我遇到了同样的问题,正在尽我所能解决。你解决了吗?嘿,使用这种策略,这不是
res.locals.csrfToken=req、 csrfToken()
每次为每个请求生成一个新的令牌?如果我们只在会话期间生成一个令牌可以吗?这样做的最佳方式是什么?谢谢!我在使用repo hachathon-starter时遇到了相同的问题。将表单操作设置为书面形式对我来说是解决问题的方法。但是,在很少的情况下,我注意到我仍然是g设置禁止的错误。经过一些调查,我意识到我必须在表单操作中编码csrf值。
var fs = require('fs');
var async = require('async');
var express = require('express');
var formidable = require('formidable');
var app = express();

app.use(express.urlencoded())
  .use(express.json())
  .use(express.cookieParser())
  .use(express.session())
  .use(express.csrf())

app.get('/upload', function(req, res) {
  // File uploads ignored.
  res.render('upload', {_csrf:req.csrfToken()});
});

app.post('/upload', function(req, res) {
  // Explicitly handle uploads
  var form = new formidable.IncomingForm();
  form.uploadDir = 'temp';

  var count = 0;
  var maxAllowed = 10;

  form.onPart = function(part) {
    if (!part.filename) return form.handlePart(part);

    count++;

    // Ignore any more files.
    if (count > maxAllowed) return part.resume();

    form.handlePart(part);
  };

  form.parse(req, function(err, fields, files) {
    // Process the files. If you don't need them, delete them.
    // Note that you should still reap your temp directory on occasion.

    async.map(Object.keys(files), function(key, cb) {
      fs.unlink(files[key].path, cb);
    }, function(err) {
      res.end();
    });
   });
});
app.use(express.methodOverride());
app.use(express.bodyParser());
app.use(express.static(__dirname + '/public'));
app.use(express.csrf());
app.use(function(req, res, next){
    res.locals.token = req.csrfToken();
    next();
});