为什么 AJAX 请求会触发 400 响应,尽管使用安全组件解锁了操作?

Why do AJAX requests trigger a 400 response despite unlocking the actions with the security component?

我正在尝试让 ajax 请求在 CakePHP4 中工作,但将 运行 保留在 CSRF 保护中。

在我的控制器中:

 public function beforeFilter(EventInterface $event)
    {
        parent::beforeFilter($event);

        $this->Security->setConfig('unlockedActions', ['players']);
    }

public function players()
    {
        $players = ['Sjaak Afhaak', 'Aad Kippezaad', 'Gras Kaffer', 'Tedje van Es'];

        if ($this->request->is('ajax')) {
            $this->response = $this->response->withDisabledCache();
        }

        $letter = trim($this->request->getData('letter'));
        if (!empty($letter)) {
            $players = array_filter($spelers, function ($haystack) use ($letter) {
                return(strpos($haystack, $letter));
            });
        }

        $this->set(compact('players'));
        $this->viewBuilder()->setOption('serialize', ['players']);

        $this->RequestHandler->renderAs($this, 'json');
    }

然后在模板文件中:

<?php echo $this->Html->script('jquery.min'); ?>

<div class="container">
    <div class="row">
        <div class="column">
            <div class="users form content">
                <?php echo $this->Form->create(null, ['url' => \Cake\Routing\Router::pathUrl('Ado::players')]); ?>
                <fieldset>
                    <legend>Players</legend>
                    <?php echo $this->Form->text('letter', ['placeholder' => 'Begin letter(s)']); ?>
                </fieldset>
                <?= $this->Form->button('OK'); ?>
                <?= $this->Form->end() ?>
            </div>
        </div>
        <div class="column response">
            ...
        </div>
    </div>
</div>

<script>
    $(document).ready(function () {
        $(document).on("submit", "form", function (event) {
            var $form = $(this);
            var $target = $('div.response');
            var csrf = $('input[name=_csrfToken]', $form).val();
            var data = { letter:  $('input[name=letter]', $form).val() };

            $target.html('');

            $.ajax({
                method: "POST",
                url: $form.attr('action'),
                beforeSend: function(xhr) {
                    xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
                    xhr.setRequestHeader('X-CSRF-Token', csrf);
                },
                data: data,
                dataType: "json"
            })
                .done(function (response) {
                    var items = [];
                    $.each( response, function( key, val ) {
                        items.push( "<li id='" + key + "'>" + val + "</li>" );
                    });

                    $( "<ul/>", {
                        "class": "spelers-list",
                        html: items.join( "" )
                    }).appendTo( $target );
                });

            event.preventDefault();
        });
    });
</script>

如果 ajax 调用中没有 beforeSend,我会收到 403 响应。 如果我包含 X-CSRF-Token,我会收到 400 响应。

2020-06-18 09:49:38 Error: [Cake\Http\Exception\BadRequestException] `_Token` was not found in request data. in src\vendor\cakephp\cakephp\src\Controller\Component\FormProtectionComponent.php on line 143
Stack Trace:
- src\vendor\cakephp\cakephp\src\Controller\Component\FormProtectionComponent.php:97
- src\vendor\cakephp\cakephp\src\Event\EventManager.php:309
- src\vendor\cakephp\cakephp\src\Event\EventManager.php:286
- src\vendor\cakephp\cakephp\src\Event\EventDispatcherTrait.php:92
- src\vendor\cakephp\cakephp\src\Controller\Controller.php:569
- src\vendor\cakephp\cakephp\src\Controller\ControllerFactory.php:72
- src\vendor\cakephp\cakephp\src\Http\BaseApplication.php:229
- src\vendor\cakephp\cakephp\src\Http\Runner.php:77
- src\vendor\cakephp\cakephp\src\Http\Middleware\BodyParserMiddleware.php:164
- src\vendor\cakephp\cakephp\src\Http\Runner.php:73
- src\vendor\cakephp\authorization\src\Middleware\AuthorizationMiddleware.php:129
- src\vendor\cakephp\cakephp\src\Http\Runner.php:73
- src\vendor\cakephp\authentication\src\Middleware\AuthenticationMiddleware.php:124
- src\vendor\cakephp\cakephp\src\Http\Runner.php:73
- src\vendor\cakephp\cakephp\src\Http\Runner.php:77
- src\vendor\cakephp\cakephp\src\Http\Middleware\CsrfProtectionMiddleware.php:138
- src\vendor\cakephp\cakephp\src\Http\Runner.php:73
- src\vendor\cakephp\cakephp\src\Http\Runner.php:58
- src\vendor\cakephp\cakephp\src\Routing\Middleware\RoutingMiddleware.php:166
- src\vendor\cakephp\cakephp\src\Http\Runner.php:73
- src\vendor\cakephp\cakephp\src\Routing\Middleware\AssetMiddleware.php:68
- src\vendor\cakephp\cakephp\src\Http\Runner.php:73
- src\vendor\cakephp\cakephp\src\Error\Middleware\ErrorHandlerMiddleware.php:119
- src\vendor\cakephp\cakephp\src\Http\Runner.php:73
- src\vendor\cakephp\debug_kit\src\Middleware\DebugKitMiddleware.php:60
- src\vendor\cakephp\cakephp\src\Http\Runner.php:73
- src\vendor\cakephp\cakephp\src\Http\Runner.php:58
- src\vendor\cakephp\cakephp\src\Http\Server.php:90
- src\webroot\index.php:41

不确定是否相关,但我正在使用身份验证插件。 (https://book.cakephp.org/authentication/2/en/index.html)

从堆栈跟踪中可以看出,错误源于 form protection component,而不是安全组件,因此对安全组件的解锁操作不会执行任何操作。

安全组件已弃用(Cookbook 似乎没有提及),表单保护组件是要替换它的实用程序之一(其他是 CSRF middleware and the HTTPS enforcer middleware)- 你不应该同时使用两者,删除安全组件(我的意思是也删除相关的 loadComponent() 调用),并相应地配置表单保护组件!

$this->FormProtection->setConfig('unlockedActions', ['players']);

这里的文档确实需要大修,不仅安全组件没有弃用通知,而且表单保护组件也没有列在组件部分。