13

我已经以这样的方式实现了 csrf(跨站点请求伪造)保护:

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

这很好用。Angularjs 在通过 $http 服务发出的所有请求中都使用了 csrf 令牌。我通过我的 Angular 应用程序提出的请求效果很好。

我的问题是测试这些 api 端点。我正在使用 mocha 来运行我的自动化测试和请求模块来测试我的 api 端点。当我使用请求模块向使用 csrf(POST、PUT、DELETE 等)的端点发出请求时,即使它正确使用了 cookie 等,它也会失败。

有没有其他人想出解决这个问题的方法?有人需要更多信息吗?

测试示例:

function testLogin(done) {
  request({
    method: 'POST',
    url: baseUrl + '/api/login',
    json: {
      email: 'myemail@email.com',
      password: 'mypassword'
    } 
  }, function (err, res, body) {
    // do stuff to validate returned data
    // the server spits back a 'FORBIDDEN' string,
    // which obviously will not pass my validation
    // criteria
    done();
  });
}
4

3 回答 3

11

诀窍是您需要将 POST 测试包装在 GET 中并从 cookie 中解析必要的 CSRF 令牌。首先,假设您创建了一个与 Angular 兼容的 CSRF cookie,如下所示:

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

然后,您的测试可能如下所示:

describe('Authenticated Jade tests', function () {
  this.timeout(5000);

  before(function (done) {
    [Set up an authenticated user here]
  });

  var validPaths = ['/help', '/products'];

  async.each(validPaths, function (path, callback) {
    it('should confirm that ' + path + ' serves HTML and is only available when logged in', function (done) {
      request.get('https://127.0.0.1:' + process.env.PORT + path, function (err, res, body) {
        expect(res.statusCode).to.be(302);
        expect(res.headers.location).to.be('/login');
        expect(body).to.be('Moved Temporarily. Redirecting to /login');

        var csrftoken = unescape(/XSRF-TOKEN=(.*?);/.exec(res.headers['set-cookie'])[1]);
        var authAttributes = { _csrf: csrftoken, email: userAttributes.email, password: 'password' };

        request.post('https://127.0.0.1:' + process.env.PORT + '/login', { body: authAttributes, json: true }, function (err, res) {
          expect(res.statusCode).to.be(303);

          request.get('https://127.0.0.1:' + process.env.PORT + path, function (err, res, body) {
            expect(res.statusCode).to.be(200);
            expect(body.toString().substr(-14)).to.be('</body></html>');

            request.get('https://127.0.0.1:' + process.env.PORT + '/bye', function () {
              done();
            });
          });
        });
      });
    });

    callback();
  });
});

这个想法是实际登录并使用发布您从 cookie 中获取的 CSRF 令牌。请注意,您需要在 mocha 测试文件的顶部添加以下内容:

var request = require('request').defaults({jar: true, followRedirect: false});
于 2013-09-13T01:47:08.670 回答
1

我所做的是仅在非生产中公开一个 csrf 令牌:

if (process.env.NODE_ENV !== 'production') {
  app.use('/csrf', function (req, res, next) {
    res.json({
      csrf: req.csrfToken()
    })
  })
}

然后让它成为第一个测试并将其保存为全局。您必须在测试中使用代理,以便始终使用相同的会话。

于 2013-09-13T00:00:21.697 回答
1

@dankohn的出色回答非常有帮助。从那时起,关于 supertest 和csurf模块的情况发生了一些变化。因此,除了那个答案之外,我发现需要将以下内容传递给 POST:

  it('should ...', function(done) {
    request(app)
      .get('/...')
      .expect(200)
      .end(function(err, res) {
        var csrfToken = unescape(/XSRF-TOKEN=(.*?);/.exec(res.headers['set-cookie'])[1]);
        assert(csrfToken);
        request(app)
          .post('/...')
          .set({cookie: res.headers['set-cookie']})
          .send({
            _csrf: csrfToken,
            ...
          })
          .expect(200)
          .end(done);
      });
  });
于 2016-08-15T09:54:06.230 回答