cakephp3.7 测试中的 CSRF 令牌不匹配

Posted

技术标签:

【中文标题】cakephp3.7 测试中的 CSRF 令牌不匹配【英文标题】:CSRF token mismatch in cakephp3.7 tests 【发布时间】:2019-10-03 20:44:12 【问题描述】:

测试/TestCase/Controller/FeedbackControllerTest.php:45

public function testAdd()

    $this->enableCsrfToken();
    $this->enableSecurityToken();
    $this->session([
        'Auth' => [
            'User' => [
                'id' => 1,
                'role' => 'REPR',
            ]
        ]
    ]);
    $this->configRequest([
        'headers' => ['Accept' => 'application/json']
    ]);
    $_data = [
        'crash' => 1,
        'details' => 'Lorem ipsum dolor sit amet'
    ];
    $_data = json_encode($_data, JSON_PRETTY_PRINT);
    $this->post('/feedback/add', $_data); // <---- 45
    $expected = [
        'status' => 'success'
    ];
    $expected = json_encode($expected, JSON_PRETTY_PRINT);
    $this->assertEquals($expected, (string)$this->_response->getBody());

PHPUnit 输出:

1) App\Test\TestCase\Controller\FeedbackControllerTest::testAdd
Cake\Http\Exception\InvalidCsrfTokenException: Missing CSRF token cookie

/vagrant/vendor/cakephp/cakephp/src/Http/Middleware/CsrfProtectionMiddleware.php:196
/vagrant/vendor/cakephp/cakephp/src/Http/Middleware/CsrfProtectionMiddleware.php:120
/vagrant/vendor/cakephp/cakephp/src/Http/Middleware/CsrfProtectionMiddleware.php:106
/vagrant/vendor/cakephp/cakephp/src/Http/Runner.php:65
/vagrant/vendor/cakephp/cakephp/src/Http/Runner.php:51
/vagrant/vendor/cakephp/cakephp/src/Routing/Middleware/RoutingMiddleware.php:168
/vagrant/vendor/cakephp/cakephp/src/Http/Runner.php:65
/vagrant/vendor/cakephp/cakephp/src/Routing/Middleware/AssetMiddleware.php:88
/vagrant/vendor/cakephp/cakephp/src/Http/Runner.php:65
/vagrant/vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php:96
/vagrant/vendor/cakephp/cakephp/src/Http/Runner.php:65
/vagrant/vendor/cakephp/cakephp/src/Http/Runner.php:51
/vagrant/vendor/cakephp/cakephp/src/Http/Server.php:98
/vagrant/vendor/cakephp/cakephp/src/TestSuite/MiddlewareDispatcher.php:201
/vagrant/vendor/cakephp/cakephp/src/TestSuite/IntegrationTestTrait.php:516
/vagrant/vendor/cakephp/cakephp/src/TestSuite/IntegrationTestTrait.php:413
/vagrant/tests/TestCase/Controller/FeedbackControllerTest.php:45

我已经阅读并尝试了答案中的解决方案:

How to create CSRF token for Cakephp 3 PHPunit testing?

如果我像@ndm 所说的那样添加:

$token = 'my-csrf-token';

$this->cookie('csrfToken', $token);

$data = [
    'email' => 'info@example.com',
    'password' => 'secret',
    '_csrfToken' => $token
];

然后:

Cake\Http\Exception\InvalidCsrfTokenException: CSRF 令牌不匹配。

如何解决?

【问题讨论】:

自从引入了enableCsrfToken() 方法以来,不再需要手动设置cookie。尝试从您的测试代码中删除一些位,例如请求配置、安全令牌、会话配置等,看看它是否有任何不同,也许存在某种不兼容。还要确保您没有更改中间件的默认 cookie 名称配置。 哦,等等,您正在发布一个 JSON 字符串... IIRC 在这种情况下您仍然需要手动执行此操作,但您必须将令牌作为标头传递 (X-CSRF-Token)而不是在发布数据中(现在无法测试)。 @ndm 如何将有效令牌作为 X-CSRF-Token 传递? 您将其作为标题传递,就像您已经在使用 Accept 标题一样。 我刚刚看过,你肯定需要在传递 JSON 字符串时将其作为标头发送。 【参考方案1】:

当将字符串作为 POST 数据传递时,集成测试用例不会自动设置令牌,CSRF 令牌和安全令牌都不会,因为它无法在不知道数据格式的情况下向字符串注入任何内容。因此它也不会设置 cookie。

因此,在您传递字符串数据的情况下,您必须手动设置 cookie 和令牌,与您链接的答案中描述的类似。但是,当使用除application/x-www-form-urlencoded 数据以外的任何数据(即 PHP 将解码并放入 $_POST 超全局的数据)时,在您的示例 JSON 数据中,您必须将令牌作为标头传递,因为 JSON 输入数据将是由请求处理程序组件解码(计划将其移至中间件层 IIRC),该组件在 CSRF 中间件之后运行,因此不会看到任何发布数据。

例子:

$token = 'my-csrf-token';
$this->cookie('csrfToken', $token);

$this->configRequest([
    'headers' => [
        'X-CSRF-Token' => $token,
        // ...
    ]
]);

另一方面,安全组件令牌必须进入 POST 数据,安全组件不会查找标头,并且在请求处理程序组件运行后它可以访问解码的数据(确保您在安全组件之前加载请求处理程序组件!)。您可以参考\Cake\TestSuite\IntegrationTestTrait::_addTokens() 来源来了解如何构建安全令牌,您可以这样做:

$url = '/feedback/add';

$_data = [
    'crash' => 1,
    'details' => 'Lorem ipsum dolor sit amet'
];

$keys = array_map(
    function ($field) 
        return preg_replace('/(\.\d+)+$/', '', $field);
    ,
    array_keys(Hash::flatten($_data))
);

$tokenData = $this->_buildFieldToken($url, array_unique($keys));

$_data['_Token'] = $tokenData;
$_data['_Token']['debug'] = 'SecurityComponent debug data would be added here';

请注意,传递给_buildFieldToken() 的 URL 还必须包含可能的查询字符串数据!

【讨论】:

【参考方案2】:

您能否在您的 jquery ajax 函数中尝试此代码,

beforeSend: function (xhr) 

    xhr.setRequestHeader('X-CSRF-Token', $('[name="_csrfToken"]').val());
,

将代码放在成功函数之前

【讨论】:

正如我发布的,我们在 CakePHP 中使用 PhpUnit 进行一些测试时遇到问题

以上是关于cakephp3.7 测试中的 CSRF 令牌不匹配的主要内容,如果未能解决你的问题,请参考以下文章

在测试中获取 CSRF 令牌,“CSRF 令牌无效” - 功能性 ajax 测试

在测试中获取 CSRF 令牌

每个响应中的 CSRF Token

为啥我在 laravel 中运行测试时收到“CSRF 令牌不匹配”?

如何使用 CSRF 令牌测试 Laravel / Sanctum 端点

带有 CSRF 令牌和会话的 Symfony 4 功能形式测试