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 Coo​​kie を次のように作成することを前提としています。

.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

@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 に答える
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 に答える