Error: The action you requested is not allowed. My API is blocked by filters when the HTTP request method is "POST" in CodeIgniter 4

Error: The action you requested is not allowed. My API is blocked by filters when the HTTP request method is "POST" in CodeIgniter 4

我正在使用 CI4 与 API 一起为 Android 构建 Web 应用程序。

对于 Web 应用程序,我有一个过滤器来检查用户是否已经登录,但有一些例外情况,其中之一是如果 URL 包含 [=60,则忽略过滤器=]/*(API 的 url 是 http:///localip/api/)

如果请求方法是 GET,API 工作正常。我可以从 API 获取数据。但是当我尝试使用 POST 方法将数据插入数据库时​​,它会将我重定向到登录页面(我正在使用 Postman 测试 API)

我该如何解决这个问题?

到目前为止我尝试的是将登录过滤器别名添加到

public $methods = [
        'post' => ['csrf', 'loginfilter']
    ]; But still not working

这是完整的代码

Filters.php

<?php

namespace Config;

use App\Filters\CorsFilter;
use App\Filters\LoginFilter;
use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Filters\CSRF;
use CodeIgniter\Filters\DebugToolbar;
use CodeIgniter\Filters\Honeypot;
use CodeIgniter\Filters\InvalidChars;
use CodeIgniter\Filters\SecureHeaders;

class Filters extends BaseConfig
{
    /**
     * Configures aliases for Filter classes to
     * make reading things nicer and simpler.
     *
     * @var array
     */
    public $aliases = [
        'loginfilter' => LoginFilter::class,
        'cors' => CorsFilter::class
    ];

    /**
     * List of filter aliases that are always
     * applied before and after every request.
     *
     * @var array
     */
    public $globals = [
        'before' => [
            // 'honeypot',
            'csrf',
            'loginfilter' => ['except' => ['/', '/login', 'api/*']],
            'cors'
            // 'invalidchars',
        ],
        'after' => [
            'toolbar',
            // 'honeypot',
            // 'secureheaders',
        ],
    ];

    /**
     * List of filter aliases that works on a
     * particular HTTP method (GET, POST, etc.).
     *
     * Example:
     * 'post' => ['csrf', 'throttle']
     *
     * @var array
     */
    public $methods = [
        'post' => ['csrf','loginfilter]
    ];

    /**
     * List of filter aliases that should run on any
     * before or after URI patterns.
     *
     * Example:
     * 'isLoggedIn' => ['before' => ['account/*', 'profiles/*']]
     *
     * @var array
     */
    public $filters = [];
}

LoginFilter.php

<?php

namespace App\Filters;

use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;

class LoginFilter implements FilterInterface
{

    public function before(RequestInterface $request, $arguments = null)
    {
        $session = session();
        if (!$session->has('user_id')) {
            return redirect()->to(base_url() . '/');
        }
    }

    public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
    {
    }
}

Routes.php

$routes->resource("api/role", ['controller' => 'apis\MasterDataRoleApi']);

MasterDataRoleApi.php(控制器)

<?php

namespace App\Controllers\apis;

use App\Models\GeneralModels;
use App\Models\RoleModel;
use CodeIgniter\API\ResponseTrait;
use CodeIgniter\RESTful\ResourceController;

class MasterDataRoleApi extends ResourceController
{
    use ResponseTrait;
    protected $model;
    protected $generalModel;

    public function __construct()
    {
        $this->model = new RoleModel();
        $this->generalModel = new GeneralModels();
    }

    public function index()
    {
        $role= $this->request->getVar('role');
        $data = $this->model->getRoleApi($role);
        return $this->respond($data, 200);
    }

    public function create()
    {
        $roleName = $this->request->getPost('role_name');
        $supervisor = $this->request->getPost('supervisor');
        $userId = $this->request->getVar("userId");
        helper('idgenerator');
        $maxCode = $this->generalModel->getMaxData('tmrole', 'role_id');
        $generatedId = idGenerator($maxCode[0]['role_id'], 4, 3, "JAB-");

        $this->model->insertTmRole($generatedId, $roleName, $userId, $userId);
       

        $data = array();

        $dataArr = array(
            "response" => "Success",
            "response_details" => "Saved Successfully"
        );

        $data[] =  $dataArr;

        return $this->respondCreated($data, 201);
    }
}

下图显示请求方法为 GET https://i.stack.imgur.com/2ZKPd.png

时返回的 Json

下图显示了请求方法为 POST 时返回的登录页面 https://i.stack.imgur.com/11R0z.png

提前致谢。

据我所知,每个 POST 方法都有 loginfilter 作为后备。那可能是那边的罪魁祸首。

话虽这么说,这里有一个替代解决方案。您可以将 routes.php 中的路由分组,然后将 loginfilter 应用于这些路由。此外,您可以嵌套它们并根据需要对其进行分区。

示例:

$routes->group(
    'api',
    ['filter' => 'loginfilter'],
    function ($routes) {
        $routes->resource("role", ['controller' => 'apis\MasterDataRoleApi']);
    }
);

您可以在使用此方法时删除全局过滤器。

A 部分:CSRF

解释 1:

Image below shows Json returned when request method is GET https://i.stack.imgur.com/2ZKPd.png

您的 API GET 请求工作正常,因为它们 受保护。

Cross-site request forgery (CSRF)

The CSRF Protection is only available for POST/PUT/PATCH/DELETE requests. Requests for other methods are not protected.

解释二:

Image below shows login page returned when request method is POST https://i.stack.imgur.com/11R0z.png

错误

The action you requested is not allowed.

那个错误来自system/Security/Security.php::verify()

throw SecurityException::forDisallowedAction();

您通常会在两种情况下收到此错误:

  1. 当您忘记随请求一起提交 CSRF 令牌时 (POST/PUT/etc.).
  2. 当与您的 HTML form/request body 一起提交的 CSRF 令牌与 CSRF cookie 中存在的令牌不匹配时 .

After having turned on the CSRF filter,当您发出 HTTP 请求并且缺少 CSRF cookie 时,其中一个是 auto-generated 并在 HTTP Set-Cookie: '...' 响应 header 中发送。

如果您正在使用 默认 Cookie-based CSRF 保护,以及任何进一步的 (POST/PUT/etc.) 提出请求后,您需要随请求一起提交 匹配 CSRF 令牌。

Double Submit Cookie

This technique is easy to implement and is stateless. In this technique, we send a random value in both a cookie and as a request parameter, with the server verifying if the cookie value and request value match. When a user visits (even before authenticating to prevent login CSRF), the site should generate a (cryptographically strong) pseudorandom value and set it as a cookie on the user's machine separate from the session identifier. The site then requires that every transaction request include this pseudorandom value as a hidden form value (or other request parameter/header). If both of them match at server side, the server accepts it as legitimate request and if they don't, it would reject the request.

CSRF 解决方案:

要通过Double Submit Cookie测试,每次发出(POST/PUT/etc.)请求时,您将需要提交 CSRF cookie 和具有 匹配 CSRF 令牌的请求 parameter/header。即:

要求1: Cookie: csrf_cookie_name=ccd8facfa8229bdba5e0160c108d1a02; HTTP请求header.

要求二:

The Order of Token Sent by Users

检查CSRF令牌可用性的顺序如下:

  1. $_POST数组(即:<input type="hidden" name="<?= csrf_token() ?>" value="<?= csrf_hash() ?>" />)。
  2. HTTP header(即:X-CSRF-TOKEN: ccd8facfa8229bdba5e0160c108d1a02)。
  3. php://input(JSON 请求)- 请记住,这种方法是最慢的方法,因为我们必须解码 JSON 然后 re-encode 它。 (即:{"csrf_cookie_name": "ccd8facfa8229bdba5e0160c108d1a02"}

要求 1 通常由浏览器 auto-fulfilled 如果这是您发起请求的地方。尽管如此,您还是可以手动完成它。即:在您的 PHP 主视图模板中,您可以使用 Ajax:

<script>
        $.ajaxSetup({
            headers: { 
                "<?php echo csrf_header();?>": "<?php echo csrf_hash();?>",

                "Cookie": "<?php echo csrf_token();?>"="<?php echo csrf_hash();?>"; "<?php echo session_name();?>":"<?php echo session_id();?>"
             }
        });

<script>


以下是我的个人 .env 文件配置,对我有用。

myapp.local 更改为您的应用程序的域。

.env 文件.

app.baseURL = 'http://myapp.local'
app.sessionExpiration = 86400

cookie.domain = '.myapp.local'
cookie.httponly = false

security.expires = 86400
security.regenerate = false

SECURITY/CSRF、COOKIE、APP/SESSION相关的其余.env文件配置保持默认设置。

当我结束 CSRF 部分时,如果您仍然有问题,请安装并配置 Xdebug,在此方法中添加断点 system/Security/Security.php::verify() 并确认您的 HTTP 请求是否通过了其中的条件。确保您的 Xdebug 配置类似于:(xdebug.log_level = 0 | xdebug.mode = debug,develop | xdebug.start_with_request = yes | xdebug.client_port = 9004 | xdebug.client_host = "localhost" | xdebug.trace_output_name = "trace.%c.%t-%s.%H_%R" | xdebug.profiler_output_name = "cachegrind.out.%t-%s.%H_%R" | xdebug.remote_handler = "dbgp" | xdebug.show_local_vars = 9)

B 部分:Postman OR Insomnia

本节介绍如何设置您的 REST API 客户端应用程序工具,以便能够测试您项目的 end-points。

HTTP请求Header要求:

  1. X-Requested-With: XMLHttpRequest
  2. X-CSRF-TOKEN: 62b04a891414ef789bee7108f94ad97a
  3. Content-Type: application/x-www-form-urlencoded
  4. Accept: */*
  5. Cookie: csrf_cookie_name=62b04a891414ef789bee7108f94ad97a; ci_session=4ji7amn186ckbo0gutdoe3ai6ufumk4e
  6. User-Agent: insomnia/2022.1.1
  7. Host: myapp.local

以上header为必填。您可以在上面列出的请求之后添加其他 HTTP 请求 headers。

User-Agent 可能会有所不同,具体取决于您使用的是一种工具还是另一种工具(即 Insomnia 或 Postman)。

Host 也可能有所不同,具体取决于您如何设置应用程序的基础 URL。

Content-Type 可能会有所不同,具体取决于您提交给服务器的数据类型。 (即 HTML 表格 -> application/x-www-form-urlencoded 和 JSON -> application/json)。

至于 Cookie header,您可能需要附加 ci_session (session_id) cookie,以防您的端点位于身份验证系统或登录之后筛选。当然,如果您的 API 使用不同的 Auth 机制(即:使用 Bearer/Access tokens with an extra Authorization: Bearer xxx_my_bearer_token_here header),这可能 而不是 是必要的。 此外,如果您的 application has enabled the csrf filter,您可能需要附加一个 csrf_cookie_name cookie,如上所示。在这种情况下,您可能希望专门创建一个专用的 GET API 路由端点,以允许您接收 csrf cookie 名称和值(哈希)。或者,如果您懒得设置专用端点来发送 CSRF 令牌,并且您已经拥有网络应用程序 运行,请在通过您的应用程序登录 in/navigating 时打开浏览器的控制台,在 Application TAB -> Cookies -> your domain,你应该能够在 Postman / Insomnia 中看到并使用这些 cookies。

X-CSRF-TOKEN HTTP 请求 header 值应与 csrf_cookie_name cookie 值相同

HTTP请求调用要求:

即:

POST http://myapp.local/api/companies

请求Body。不要忘记添加 csrf_cookie_name 令牌作为请求的一部分 body.

Key Value
company_name Tesla, Inc.
csrf_cookie_name 62b04a891414ef789bee7108f94ad97a

正如我在 B 部分中总结的那样:

Important items Description
ci_session cookie or an Authorization: Bearer xxxxxxx HTTP request header. Allows you to authenticate with your application/project only for Auth protected API endpoints. In your particular case, I believe your loginfilter is working with a ci_session cookie and the cookie is expected to be sent along with every request with the help of the Cookie HTTP request header.
csrf_cookie_name cookie and (a X-CSRF-TOKEN HTTP request header or CSRF token request parameter). The CSRF cookie and (X-CSRF-TOKEN HTTP request header or CSRF token request parameter) values MUST match. This is a requirement if you've turned on the csrf filter.