【问题标题】:How to test endpoints protected by csrf in node.js/express如何在 node.js/express 中测试受 csrf 保护的端点
【发布时间】:2013-09-17 09:43:42
【问题描述】:

我已经以这样的方式实现了 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();
  });
}

【问题讨论】:

  • 你能放一个你尝试过的测试样本吗?

标签: node.js angularjs express request csrf


【解决方案1】:

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

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

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

【讨论】:

  • 我将如何在上面的请求中使用 csrf 令牌?如果我将 jar: true 添加到请求对象,它会使用 cookie,我可以看到正在使用的令牌和 cookie,但由于某种原因它不接受它。那么如何使用请求中的令牌使其接受呢?
  • 你必须提交它。即x-csrf-token: csrf 或将其添加到请求正文中。是的,这很烦人,但这正是用户必须做的。确保会话不会因每个请求而改变。
  • 我一直在注销测试端和服务器端的请求和响应对象,并且令牌在顺序请求中是相同的。它就在那里,就像我只是使用站点的前端访问那些相同的端点时一样。 cookie 就在那里,它会在第一个请求之后的每个请求中添加到请求正文中。
  • 这种“烦恼”很好,因为这是一个测试,可以确保表单确实需要 csrf 来验证并且不会忽略它。我不喜欢这种方法是你必须事先提出请求。但是我想不出一个在不启动会话的情况下获取(或存根)令牌的好方法。
【解决方案2】:

诀窍是您需要将 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});

【讨论】:

  • 这看起来就像它会做的那样。我希望我不必嵌套这样的请求,所以我可以在运行测试时禁用 csrf。
  • 这确实有点痛苦,但如果你不考虑 CSRF,你就无法真正进行端到端测试。
  • 那是真的......我也考虑过使用不同的测试框架,比如 phantomjs,我可以测试更多与应用程序的交互,而不仅仅是端点,但我想它会是两者都很好。感谢您的帮助!
  • 它与 node.js 配合得非常好!感谢您分享关于 cookie 使用的想法!
  • 如果 XSRF-TOKEN cookie 不可用并且您不想将其添加到服务器中,您可以使用 res.text.match(/name=_csrf value=([^&gt;]*)&gt;/)[1] 从登录页面的 html 表单中获取 csrf 令牌。
【解决方案3】:

@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);
      });
  });

【讨论】:

    猜你喜欢
    • 2018-03-18
    • 1970-01-01
    • 2019-06-27
    • 2012-05-22
    • 2020-05-09
    • 2020-02-10
    • 2011-08-21
    • 2013-10-30
    • 2022-01-26
    相关资源
    最近更新 更多