Node Express 和 csurf - 403(Forbidden)无效的 CSRF 标记

5

我查看了许多信息,并尝试了各种方法,但都无法解决问题。我使用Node、Express、EJS,并尝试在一个使用jQuery ajax发布的表单上使用csurf。无论如何配置csurf,我都会收到“403(禁止)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约两周了,所以请原谅我的无知。

4个回答

7

如果你想将令牌存储在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}}" }
}); 

我曾经尝试过那个方法,但基于我找到的其他样例,我将其删除了。你说得对,我需要用它来处理cookie,但最终让它工作的并不是这个。不过还是谢谢你的回答! - Tsar Bomba

1
另一种处理个人项目的方法是在成功提交表单时重新发送一个新的令牌:
例如,在我的表单上(包括文件上传),我有以下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()});
});

1

经过几个小时的故障排除和搜索,我找到了一篇帖子,帮助解答了问题。我所需要做的就是在ajax post中传递头值。很有道理,我只是忽略了它。代码如下:

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

...然后使用jQuery:
    $.ajaxSetup({
        headers: {"X-CSRF-Token": $("#_csrf").val()}
    });

这是我的回答,当我说你需要在实际的AJAX请求中发送它时,它所表达的意思,只是我没有提供代码示例。我已经更新了我的答案,并提供了一个示例供其他人参考。 - Ash
@AshleyB - 很好,你应该得到这个功劳。在我找到可行的方法之前,我尝试了几种其他变化形式,所以一个例子对每个人来说肯定是有帮助的。谢谢! - Tsar Bomba

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接