AngularJS和Express中的CSRF令牌失效

4

我希望在使用MEAN堆栈的应用程序中添加CSRF保护。

我已经尝试了其他人提供的答案:在ExpressJS中实现CSRF保护,但那个回答是针对旧版本的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();
});

我可以看到在我的应用程序中(使用Chrome检查),生成了一个名为XSRF-TOKEN的令牌。
但是,当我提交表单(Angular前端)时,出现了有关令牌的错误: {"message":"invalid csrf token","error":{"expose":true,"code":"EBADCSRFTOKEN","statusCode":403,"status":403}} 我错过了什么吗?我在想req.csrfToken()是否生成了由Angular提供的正确令牌...
编辑:
我刚刚发现XSRF-TOKEN仅在AngularJS的$http请求中使用。因此,我认为我必须在我的表单中添加一个隐藏输入来提交csrf值,以便由Express进行验证,但如何实现呢?
3个回答

12

最终我成功发送了好的令牌值。以下是完整的答案。

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到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)。 示例:

现在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端口上。 - Rahul Arora
是的,您可以查看此答案以获得更好的想法:https://dev59.com/q14c5IYBdhLWcg3wia5P#33175322 - jbltx
这是否意味着我需要实现一个拦截器,手动传递cookie值到Angular JS头部? - Rahul Arora
如AngularJS文档所述,它会自动检查cookie是否存在,并在XHR请求期间将其设置为HTTP标头(使用$http,无需拦截器)。如果您需要在不使用$http的情况下进行请求,则可以在标头中每次传递$csrf,或者创建一个拦截器。 - jbltx
谢谢!你的回答真帮了我大忙。干杯! - Rahul Arora

1

如果有人遇到“csurf配置错误”,请尝试以下方法:

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

0

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