【问题标题】:Bad CSRF token with AngularJS and Express带有 AngularJS 和 Express 的错误 CSRF 令牌
【发布时间】:2015-02-08 00:43:18
【问题描述】:

我想在使用 MEAN 堆栈的应用中添加 CSRF 保护。

我尝试了某人已经给出的答案:CSRF Protection in ExpressJS

但这是针对较旧的 express 版本,所以我做了一些更改:

app.use(cookieParser(config.cookieSecret, { httpOnly: true }));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(session({ secret: config.sessionSecret, resave: false, saveUninitialized: true }));
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());
app.use(csrf({value: function(req) {
        var token = (req.body && req.body._csrf)
            || (req.query && req.query._csrf)
            || (req.headers['x-csrf-token'])
            || (req.headers['x-xsrf-token']);
        return token;
    }
}));
app.use(function(req, res, next) {
  res.cookie('XSRF-TOKEN', req.csrfToken());
  next();
});

我可以看到名为 XSRF-TOKEN 的令牌在我的应用程序上生成良好(使用 Chrome 检查)。

但是当我发布表单(Angular 前端)时,我有一个关于令牌的错误:

{"message":"invalid csrf token","error":{"expose":true,"code":"EBADCSRFTOKEN","statusCode":403,"status":403}}

我错过了什么吗?我想知道req.csrfToken() 是否会生成 Angular 给出的良好令牌...

编辑:

我只看到XSRF-TOKEN 被AngularJS 在$http 请求中使用。所以我想我必须在我的表单中添加一个隐藏的输入来发布带有 csrf 值的帖子,以便由 Express 进行检查,但是如何?

【问题讨论】:

    标签: angularjs cookies express csrf passport.js


    【解决方案1】:

    最后我能够发送良好的令牌价值。这是完整的答案。

    AngularJS 在使用$http 服务发出请求期间使用 CSRF 保护(Angular 称为 XSRF)。

    在执行 XHR 请求时,$http 服务从 cookie(默认情况下,XSRF-TOKEN)并将其设置为 HTTP 标头 (X-XSRF-TOKEN)。因为只有在您的域上运行的 JavaScript 才能 读取 cookie,您的服务器可以确定 XHR 来自 在您的域上运行的 JavaScript。标题不会设置为 跨域请求。

    有很多帖子解释了如何使用 ExpressJS 3.xx 发送 XSRF-TOKEN,但是随着 4.xx 版本 的变化,有些事情发生了变化。 Connect 中间件不再包括在内。 Express 使用自己的中间件:cookie-parserbody-parserexpress-sessioncsurf

    1 - 发送 XSRF-TOKEN cookie

    第一步是发送cookie,从后端到前端(Express to Angular):

    var express         = require('express');
    var app             = express();
    var cookieParser    = require('cookie-parser');
    var bodyParser      = require('body-parser');
    var session         = require('express-session');
    var config          = require('./lib/config'); //config.js file with hard-coded options.
    var csrf            = require('csurf');
    
    app.use(cookieParser(config.cookieSecret, { httpOnly: true }));
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: false }));
    app.use(session({ 
        name: 'sessionID', 
        secret: config.sessionSecret, 
        cookie: {
            path: '/',
            httpOnly: true,
            secure: false,
            maxAge: 3600000
        },
        rolling: true, 
        resave: false, 
        saveUninitialized: true 
    }));
    app.use(csrf());
    app.use(function(req, res, next) {
        res.cookie('XSRF-TOKEN', req.csrfToken());
        next();
    });
    

    现在 Angular 能够在 $http 请求期间设置其 HTTP 标头 (X-XSRF-TOKEN)。示例:

    <body ng-app="testApp">
        <div ng-controller="LoginCtrl">
            <form role="form" ng-submit="login()" >
               <input type="email" ng-model="user.email" />
               <input type="password" ng-model="user.password" />
               <input type="submit" value="Log In" />
            </form>
            <p style="color:red">{{loginMsg}}</p>
        </div>
    </body>
    
    <script>
        var app = angular.module('testApp', ['ngResource']);
        app.controller('LoginCtrl', function ($scope, $http) {
            $scope.user = {};
            $scope.loginMsg = '';
            $scope.login = function () {
                 $http.post('/login', $scope.user).success(function () {
                      $window.location.href='/profile';
                 }).error(function () {
                      $scope.loginMsg = 'Wrong credentials, try again.';
                 });
            };
        });
    </script>
    

    2 - 以非 Angular 形式添加 _csrf 输入

    这是我的问题,我不知道如何添加此输入。最后,我创建了一个名为 $csrf 的新 Angular 服务:

        app.factory('$csrf', function () {
            var cookies = document.cookie.split('; ');
            for (var i=0; i<cookies.length; i++) {
              var cookie = cookies[i].split('=');
              if(cookie[0].indexOf('XSRF-TOKEN') > -1) {
                return cookie[1];
              }
            }
            return 'none';
        });
    

    我在 Plunker 上做了一个例子:http://plnkr.co/edit/G8oD0dDJQmjWaa6D0x3u

    希望我的回答会有所帮助。

    【讨论】:

    • 如果您的前端和后端域不同,此解决方案是否也有效?例如,我的后端服务器在端口 4200 上运行,而前端在 3000
    • 是的,你可以看到这个答案可能有更好的主意:stackoverflow.com/a/33175322/2904349
    • 这是否意味着我必须实现一个拦截器,以便每次手动将 cookie 值传递给 Angular JS 标头?
    • 正如AngularJS文档中所说,它会在XHR请求期间自动检查cookie是否存在并将其设置为HTTP头(使用$http,不需要拦截器)。如果需要在不使用 $http 的情况下发出请求,可以在 header 中每次都传递 $csrf,或者制作一个拦截器。
    • 谢谢!你的回答真的很有帮助。干杯!
    【解决方案2】:

    如果有人收到“csurf 配置错误”,试试这个:

    app.use( csrf( { cookie: true } ));
    

    【讨论】:

      【解决方案3】:

      我已经创建了可以直接使用的存储库。

      节点

      https://github.com/nil4you/node-csrf-and-http-only-cookie-with-spa

      角度

      https://github.com/nil4you/angular-csrf-and-httponly-cookie-demo

      在这里问任何问题任何评论,因为我看不到太多的编码问题,这都是关于正确的设置问题。

      【讨论】:

        猜你喜欢
        • 2017-07-29
        • 2013-08-11
        • 2014-09-25
        • 2015-07-19
        • 2016-05-08
        • 2020-11-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多