Codeigniter 4 在 AJAX 模式中重用 CSRF 令牌

Posted

技术标签:

【中文标题】Codeigniter 4 在 AJAX 模式中重用 CSRF 令牌【英文标题】:Codeigniter 4 reuse of CSRF token in AJAX modal 【发布时间】:2021-05-27 23:47:11 【问题描述】:

场景:

我正在开发 CMS 系统,我想为对象(页面、帖子、媒体等)添加一些类别。在我看来,为了保存一个新类别,我使用放置在 Bootstrap 模式中的 html 表单,该表单通过 AJAX 发送到我的控制器。整个站点都启用了 CSRF 保护。

第一次发送数据时,我通过表单传递 CSRF 令牌名称和哈希。一旦在控制器中由 php 代码处理,我想在响应中传递 CSRF 值,以便能够在模式中“重用”表单(例如显示错误消息或/和创建另一个类别)。

然而,我无法访问 get_csrf_token_name()get_csrf_hash() 方法将值传回视图。

在我看来 admin/category/create.php:

...
<!-- CREATE CATEGORY MODAL MODAL -->
    <div class="modal" id="createCategory" tabindex="-1">
        <div class="modal-dialog modal-sm">
            <div class="modal-content">

                <div class="modal-header">
                    <h5 class="modal-title">Nová kategorie</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Zavřít"></button>
                </div>

                <div class="modal-body">  
                    <form action="" method="post" id="createCategoryForm">
                        <input type="hidden" value="<?= csrf_hash(); ?>" name="<?= csrf_token(); ?>" id="csrf">

                        <div class="form-group mb-3">
                            <label for="title" class="form-label">Název kategorie</label>
                            <input type="text" class="form-control" name="title" id="title" value="">
                        </div>   
                </div>

                <div class="modal-footer">
                        <button type="submit" class="btn btn-primary" id="createCategoryConfirm">Vytvořit novou kategorii</button>
                    </form>
                </div>


            </div>
        </div>
    </div>

...

<script>
    $('#head').on('click', '.create', function() 

        $('#createCategory').modal('show');
        $('#createCategoryForm').attr('action', '<?= base_url(); ?>/admin/category/create');

        $('#createCategoryConfirm').click(function(e)             
            e.preventDefault();         
           
            var url = $('#createCategoryForm').attr('action');
            var csrfElement = $('#csrf');
            var csrfName = csrfElement.attr('name');
            var csrfHash = csrfElement.attr('value');
            var categoryTitle = $('input[name=title]').val();

            var data = 
                [csrfName]: csrfHash,
                'title': categoryTitle
            ;
            
            console.log(data);

            $.ajax(
                type: 'ajax',
                method: 'POST',
                url: url,
                data: data,
                dataType: 'json',
                contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
                headers: 'X-Requested-With': 'XMLHttpRequest',
                success: function(result) 
                    console.log(result); 

                ,
                error: function(result) 
                    console.log(result); 
                ,
            );
        );
    );
        
</script>

在我的控制器 Category.php 中:


<?php

namespace App\Controllers\Admin;

use App\Controllers\BaseController;
use App\Models\CategoryModel;
use CodeIgniter\I18n\Time;


class Category extends BaseController 

    protected $model;
    protected $validator;
    protected $security;

    public function __construct()  
        $this->model = new CategoryModel();
        $this->validation =  \Config\Services::validation();
        $this->security =  \Config\Services::security();
        helper(['form', 'date', 'url']);
    

...

    public function create() 
        $response = [];

        // This part of code returns error
        // 
        // $response['csrf'] = array(
        //     'name' => $this->security->get_csrf_token_name(),
        //     'hash' => $this->security->get_csrf_hash()
        // );

        $response['security'] = $this->security;
        
        if ($this->request->isAJAX()) 

            $newCategory = [
                'title' => $this->request->getVar('title'),
                'slug' => url_title($this->request->getVar('title')),
                'author' => session()->get('id'),
                'created_at' => Time::now('Europe/Prague')->toDateTimeString(),
                'updated_at' => Time::now('Europe/Prague')->toDateTimeString(),
                'parent' => '0'
            ];

            $this->validation->run($newCategory, 'categoryRules');

            if (!empty($this->validation->getErrors())) 
                $this->model->save($newCategory);   
                $response['errors'] = $this->validation->getErrors();
                echo json_encode($response);
             else 
                $this->model->save($newCategory);   
                $response['success'] = 'New category was created';
                echo json_encode($response);

            
                  
    

...

在浏览器控制台中,AJAX 响应为 POST http://localhost/admin/category/create 500 (Internal Server Error) 并具有完整响应:

code: 500
file: "D:\Web\XAMPP\htdocs\lenka\app\Controllers\Admin\Category.php"
line: 38
message: "Call to undefined method CodeIgniter\Security\Security::get_csrf_token_name()"
title: "Error"

有人可以在这里看到问题吗?关于如何在 CI4 中重用 CSRF 令牌有什么好的解决方案吗?我尝试将 CSRF regenerate 的配置值设置为 true 和 false,没有效果。

【问题讨论】:

在您的 ajax 成功中,您需要使用您在控制器中创建的新 csrf 值更新隐藏的输入#csrf 好吧,这就是重点 - 我无法在控制器中生成它们:$this-&gt;security-&gt;get_csrf_token_name()$this-&gt;security-&gt;get_csrf_hash()Call to undefined method CodeIgniter\Security\Security::get_csrf_token_name() 结尾 该方法在 CI3.x 中使用,在 CI4.x 中您可以使用 &lt;input type="hidden" name="&lt;?= csrf_token() ?&gt;" value="&lt;?= csrf_hash() ?&gt;" /&gt; 参见 docs 【参考方案1】:

在 .ENV 中更新这一行代码 应用程序/配置/安全

CSRF 重新生成 = false

【讨论】:

以上是关于Codeigniter 4 在 AJAX 模式中重用 CSRF 令牌的主要内容,如果未能解决你的问题,请参考以下文章

在codeigniter的htaccess中重定向url

在 CodeIgniter 3 中重定向后 Flashdata 未清除

如何在 Codeigniter 中重定向和删除 index.php [重复]

在 CodeIgniter 中重定向整个站点不起作用

在CodeIgniter中重定向整个站点不起作用

[本地主机在Codeigniter中重定向了您太多次