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();
您通常会在两种情况下收到此错误:
- 当您忘记随请求一起提交 CSRF 令牌时 (POST/PUT/etc.).
- 当与您的 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 令牌。
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令牌可用性的顺序如下:
$_POST
数组(即:<input type="hidden" name="<?= csrf_token() ?>" value="<?= csrf_hash() ?>" />
)。
- HTTP header(即:
X-CSRF-TOKEN: ccd8facfa8229bdba5e0160c108d1a02
)。
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要求:
X-Requested-With: XMLHttpRequest
X-CSRF-TOKEN: 62b04a891414ef789bee7108f94ad97a
Content-Type: application/x-www-form-urlencoded
Accept: */*
Cookie: csrf_cookie_name=62b04a891414ef789bee7108f94ad97a; ci_session=4ji7amn186ckbo0gutdoe3ai6ufumk4e
User-Agent: insomnia/2022.1.1
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.
我正在使用 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();
您通常会在两种情况下收到此错误:
- 当您忘记随请求一起提交 CSRF 令牌时 (POST/PUT/etc.).
- 当与您的 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 令牌。
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令牌可用性的顺序如下:
$_POST
数组(即:<input type="hidden" name="<?= csrf_token() ?>" value="<?= csrf_hash() ?>" />
)。- HTTP header(即:
X-CSRF-TOKEN: ccd8facfa8229bdba5e0160c108d1a02
)。 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要求:
X-Requested-With: XMLHttpRequest
X-CSRF-TOKEN: 62b04a891414ef789bee7108f94ad97a
Content-Type: application/x-www-form-urlencoded
Accept: */*
Cookie: csrf_cookie_name=62b04a891414ef789bee7108f94ad97a; ci_session=4ji7amn186ckbo0gutdoe3ai6ufumk4e
User-Agent: insomnia/2022.1.1
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. |