Node Express 和 csurf - 403(禁止)无效 csrf 令牌

Posted

技术标签:

【中文标题】Node Express 和 csurf - 403(禁止)无效 csrf 令牌【英文标题】:Node Express and csurf - 403 (Forbidden) invalid csrf token 【发布时间】:2016-02-24 06:33:13 【问题描述】:

通过谷歌搜索查看并尝试了我在这里和其他地方可以找到的所有内容......我只是无法克服这个问题。我正在使用 Node、Express、EJS,并尝试在使用 jQuery ajax 发布的表单上使用 csurf。无论我如何配置 csurf,我都会得到“403 (Forbidden) invalid csrf token”

我尝试在 app.js 和控制器中进行全局配置。这是我在 app.js 中尝试过的:

var express = require('express');
var session  = require('express-session');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var mysql = require('mysql');
var flash = require("connect-flash");
var csrf = require("csurf");

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(cookieParser());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded(extended: false));
app.use(session(
    secret: 'somethingsecret',
    resave: true,
    saveUninitialized: true,
    httpOnly: true,
    secure: false
));
app.use(csrf());
app.use(function (req, res, next) 
    var token = req.csrfToken();
    res.cookie('XSRF-TOKEN', token);
    res.locals.csrfToken = token;
    console.log("csrf token = " + token);
    next();
);
app.use(flash());
app.use(express.static(path.join(__dirname, 'public')));

app.use(function (err, req, res, next) 
    if (err.code !== 'EBADCSRFTOKEN') return next(err);

    // handle CSRF token errors here
    res.status(403);
    res.send('form tampered with');
)

//routing
var routes = require('./routes/index');
var users = require('./routes/users');
var register = require('./routes/register');

app.use('/', routes);
app.use('/users', users);
app.use('/register', register);

...使用此控制器:

var express = require("express");
var router = express.Router();
var bodyParser = require("body-parser");
var userSvc = require("../service/userservice");

var jsonParser = bodyParser.json();

router.get("/", function(req, res, next) 
    console.log("token = " + token);
    userSvc.getAllPublicRoles(function(data) 
        res.render("register", 
            title: "Register a new account",
            roles: data
        );
    );
);

router.post("/new", jsonParser, function(req, res, next) 
    userSvc.addUser(req.body, function(result) 
        console.log("New user id = " + result.insertId);
        res.send('"success" : "Updated Successfully", "status" : 200');
    );
);

...还有这个观点:

表格:

<form id="registerForm" class="form-horizontal" method="post">
    <input type="hidden" name="_csrf" value="<%= csrfToken %>" />

ajax 调用:

        $.ajax(
            url: "/register/new",
            type: "POST",
            dataType: "json",
            data: user
        ).done(function(data) 
            if (data) 
                console.log("Success! = " + data);
            
        ).fail(function(data) 
            console.log("Something went wrong: " + data.responseText);
        );

然后我只是尝试在控制器中做所有事情,从 app.js 中删除所有引用、调用等,并使用与上面相同的表单和 ajax 调用:

var express = require("express");
var router = express.Router();
var bodyParser = require("body-parser");
var csrf = require("csurf");
var userSvc = require("../service/userservice");

var csrfProtection = csrf();
var jsonParser = bodyParser.json();

router.get("/", csrfProtection, function(req, res, next) 
    var token = req.csrfToken();
    console.log("token = " + token);
    userSvc.getAllPublicRoles(function(data) 
        res.render("register", 
            title: "Register a new account",
            csrfToken: token,
            roles: data
        );
    );
);

router.post("/new", jsonParser, csrfProtection, function(req, res, next) 
    userSvc.addUser(req.body, function(result) 
        console.log("New user id = " + result.insertId);
        res.send('"success" : "Updated Successfully", "status" : 200');
    );
);

不知道从这里去哪里。我已经使用node大约两周了,在我的业余时间,所以请原谅我的无知。

【问题讨论】:

【参考方案1】:

如果您想将令牌存储在 cookie 中而不是会话中,请让 csurf 为您创建 cookie,例如

// Store the token in a cookie called '_csrf'
app.use(csrf(cookie: true));

// Make the token available to all views
app.use(function (req, res, next)
    res.locals._csrf = req.csrfToken();
    next();
);

然后,您需要确保令牌在使用 AJAX 进行调用时可用,无论是通过 POST 数据,还是作为自定义请求标头,例如“xsrf-token”。

此时,您向表单提供令牌,而不是实际请求(使用 AJAX 发送)。

例如,您可以在 AJAX 设置中呈现令牌:

$.ajaxSetup(
   headers: "X-CSRF-Token": "csrfToken" 
); 

【讨论】:

我试过了,然后根据我找到的其他样本将其删除。你是对的,我需要它来做饼干,但这并不是最终让它起作用的原因。感谢您的回答!【参考方案2】:

经过几个小时的故障排除和搜索,我找到了一个有助于回答问题的帖子。我所需要的只是在 ajax 帖子中传递标头值。有道理,我只是忽略了它。像这样:

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

...然后在 jQuery 中:

    $.ajaxSetup(
        headers: "X-CSRF-Token": $("#_csrf").val()
    );

【讨论】:

当我说你需要在实际的 AJAX 请求中发送它时,这就是我的回答所说的,我只是没有提供代码示例来做到这一点。我已经用其他人的示例更新了我的答案。 @AshleyB - 很公平,你值得称赞。我已经尝试了其他几种变体,直到找到一种可行的变体,所以一个例子肯定对每个人都有帮助。谢谢!【参考方案3】:

除了在帖子的标题中添加“X-CSRF-Token”之外,您还想完全禁用 cookie!

var csrfProtection = csurf( cookie: false );

作者在这里提到 https://github.com/expressjs/csurf/issues/52

cookie 和 session 验证不应该结合起来——尽管这有点误导,因为他在他的文档中结合了 cookie 和 session 验证: https://github.com/expressjs/csurf#simple-express-example

【讨论】:

【参考方案4】:

我个人项目的另一种方法是在我成功提交表单时重新发送新令牌:

例如,在我的表单(文件上传)上,我有以下 html

<form id="upload_form" type="multipart/form-data" data-csrf="csrfToken" method="post" action="/data_assets">
  <input id="excell_upload" type="file" style="visible:hidden" name="data_assets"/>
</form>

在文件更改时,我会像这样触发上传:

 $('#excell_upload').on('change',function(event)

      event.preventDefault();
      var formData = new FormData($("#upload_form")[0]);

      $.ajax(
        'type':$("#upload_form").attr('method'),
        'data': formData,
        'url': $("#upload_form").attr('action'),
        'processData': false,
        'contentType': false,
        'mimeType': 'multipart/form-data',
        'headers': "X-CSRF-Token": $("#upload_form").attr('data-csrf') ,
        'beforeSend': function (x) 
           if (x && x.overrideMimeType) 
               x.overrideMimeType("multipart/form-data");
          
          $('#trigger_upload').addClass('disabled');
        ,
        'success':function(data)
          $('#upload_form').attr('data-csrf',data.csrfToken)
        ,
        'fail':function()

        ,
        'complete':function()
          $('#trigger_upload').removeClass('disabled');
        
      );
    );

如您所见,我收到了一个新的 csrf 令牌,以便能够重复使用我的表单进行新的提交。我像这样重新生成 CSRF 令牌:

app.post('/data_assets',function(req,res,next)
  res.json('csrfToken':req.csrfToken());
);

【讨论】:

以上是关于Node Express 和 csurf - 403(禁止)无效 csrf 令牌的主要内容,如果未能解决你的问题,请参考以下文章

node.js csurf 无效的 csrf 令牌

在nodejs(Express)中使用CSURF的“无效的csrf令牌”。CSRF令牌适用于第一个请求,但对所有其他请求都给出错误

CSURF 角度实现

使用 Node、Express 和 EJS 压缩 HTML?

ForbiddenError: invalid csrf token, express js.

如何使用 express .node 、 mongoose 和 ejs 删除对象