控制器过滤器

控制器过滤器允许你在控制器执行之前或之后执行操作。与 事件 不同,你可以选择应用过滤器的特定 URI。传入过滤器可以修改请求,而后置过滤器可以操作甚至修改响应,提供了很大的灵活性和能力。使用过滤器可以执行的一些常见任务示例:

  • 对传入请求执行 CSRF 保护

  • 根据角色限制站点的区域访问

  • 对某些端点执行速率限制

  • 显示“维护中”页面

  • 执行自动内容协商

  • 等等…

创建过滤器

过滤器是简单的类,实现了 CodeIgniter\Filters\FilterInterface。它们包含两个方法:before()after(),这些方法分别包含在控制器之前和之后运行的代码。你的类必须包含这两个方法,但如果不需要可以留空。过滤器骨架类如下:

<?php

namespace App\Filters;

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

class MyFilter implements FilterInterface
{
    public function before(RequestInterface $request, $arguments = null)
    {
        // Do something here
    }

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

Before 过滤器

替换请求

在任何过滤器中,你都可以返回 $request 对象,它将替换当前的请求,允许你进行更改,这些更改在控制器执行时仍然存在。

停止后续过滤器

当你有一系列过滤器时,你可能还希望在某个过滤器后停止后续过滤器的执行。你可以通过返回任何非空结果轻松地实现这一点。如果 before 过滤器返回空结果,仍将执行控制器操作或后续过滤器。

非空结果规则的一个例外是 Request 实例。在 before 过滤器中返回它不会停止执行,只会替换当前的 $request 对象。

返回响应

由于 before 过滤器是在执行控制器之前执行的,所以有时你可能希望停止控制器中的操作。

这通常用于执行重定向,如下面的示例:

<?php

namespace App\Filters;

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

class MyFilter implements FilterInterface
{
    public function before(RequestInterface $request, $arguments = null)
    {
        $auth = service('auth');

        if (! $auth->isLoggedIn()) {
            return redirect()->to(site_url('login'));
        }
    }
}

如果返回 Response 实例,将向客户端发送响应,并停止脚本执行。这对于实现 API 的速率限制很有用。请参见 Throttler 以获取示例。

After 过滤器

After 过滤器与 Before 过滤器几乎完全相同,只是你只能返回 $response 对象,并且无法停止脚本执行。这确实允许你修改最终输出,或者只是做一些最终输出的事情。这可以用于确保某些安全头正确设置,缓存最终输出,或者使用禁用词过滤器过滤最终输出。

配置过滤器

创建过滤器后,你需要配置它们的运行时机。这是在 app/Config/Filters.php 中完成的。该文件包含四个属性,允许你配置过滤器的确切运行时机。

备注

最安全的应用过滤器方法是 禁用自动路由,并 设置过滤器到路由

警告

建议你在过滤器设置中的 URI 末尾始终添加 *。因为控制器方法可能比你想象的通过不同的 URL 访问。例如,当启用 自动路由(传统) 时,如果你有 Blog::index,它可以通过 blogblog/indexblog/index/1 等方式访问。

$aliases

$aliases 数组用于将简单名称与一个或多个完全限定的类名相关联,这些类名是要运行的过滤器:

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Filters extends BaseConfig
{
    public array $aliases = [
        'csrf' => \CodeIgniter\Filters\CSRF::class,
    ];

    // ...
}

别名是强制性的,如果你稍后尝试使用完整的类名,系统将抛出错误。以这种方式定义使得切换使用的类变得简单。非常适合当你决定需要更改到不同的身份验证系统时,因为你只需要更改过滤器的类即可完成。

你可以将多个过滤器组合成一个别名,使复杂的过滤器组非常简单:

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Filters extends BaseConfig
{
    public array $aliases = [
        'apiPrep' => [
            \App\Filters\Negotiate::class,
            \App\Filters\ApiAuth::class,
        ],
    ];

    // ...
}

你应该根据需要定义尽可能多的别名。

$globals

第二部分允许你定义任何应用于框架的每个请求的过滤器。在这里使用太多可能会对性能产生影响,所以要小心。可以通过将别名添加到 before 或 after 数组来指定过滤器:

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Filters extends BaseConfig
{
    // ...

    public array $globals = [
        'before' => [
            'csrf',
        ],
        'after' => [],
    ];

    // ...
}

除了少数 URI

有时你希望将过滤器应用于几乎所有请求,但有一些应该不受影响。一个常见的示例是,如果你需要从 CSRF 保护过滤器中排除几个 URI,以允许第三方网站的请求访问一个或两个特定的 URI,同时保持其余 URI 受保护。要做到这一点,请在别名旁边添加一个包含 except 键和要匹配的 URI 值的数组:

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Filters extends BaseConfig
{
    // ...

    public array $globals = [
        'before' => [
            'csrf' => ['except' => 'api/*'],
        ],
        'after' => [],
    ];

    // ...
}

在过滤器设置中可以使用 URI 的任何位置,你都可以使用正则表达式,或者像在这个例子中使用星号 (*) 作为通配符,匹配之后的所有字符。在这个例子中,任何以 api/ 开头的 URL 都将被免于 CSRF 保护,但网站的表单将全部受保护。如果你需要指定多个 URI,可以使用 URI 模式数组:

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Filters extends BaseConfig
{
    // ...

    public array $globals = [
        'before' => [
            'csrf' => ['except' => ['foo/*', 'bar/*']],
        ],
        'after' => [],
    ];

    // ...
}

$methods

警告

如果使用 $methods 过滤器,你应该 禁用自动路由(传统),因为 自动路由(传统) 允许任何 HTTP 方法访问控制器。以你不期望的方法访问控制器可能会绕过过滤器。

你可以将过滤器应用于所有某种 HTTP 方法的请求,如 POST、GET、PUT 等。在此数组中,你需要以**全小写**指定方法名称。它的值将是要运行的过滤器数组:

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Filters extends BaseConfig
{
    // ...

    public array $methods = [
        'post' => ['foo', 'bar'],
        'get'  => ['baz'],
    ];

    // ...
}

备注

$globals$filters 属性不同,这些只能作为 before 过滤器运行。

除了标准的 HTTP 方法外,这也支持一个特殊情况:clicli 方法将应用于所有从命令行运行的请求。

$filters

该属性是一个过滤器别名数组。对于每个别名,你可以为 beforeafter 数组指定过滤器应该应用到的一系列 URI 模式:

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Filters extends BaseConfig
{
    // ...

    public array $filters = [
        'foo' => ['before' => ['admin/*'], 'after' => ['users/*']],
        'bar' => ['before' => ['api/*', 'admin/*']],
    ];

    // ...
}

过滤器参数

在配置过滤器时,可以在设置路由时向过滤器传递其他参数:

<?php

$routes->add('users/delete/(:segment)', 'AdminController::index', ['filter' => 'admin-auth:dual,noreturn']);

在这个例子中,数组 ['dual', 'noreturn'] 将在过滤器的 before()after() 实现方法的 $arguments 中传递。

确认过滤器

CodeIgniter 提供了以下 命令 来检查路由的过滤器。

filter:check

4.3.0 新版功能.

使用 GET 方法检查路由 / 的过滤器:

> php spark filter:check get /

输出如下所示:

+--------+-------+----------------+---------------+
| Method | Route | Before Filters | After Filters |
+--------+-------+----------------+---------------+
| GET    | /     |                | toolbar       |
+--------+-------+----------------+---------------+

你还可以通过 spark routes 命令查看路由和过滤器。 参见 URI 路由

提供的过滤器

CodeIgniter4 提供的过滤器有: HoneypotCSRFInvalidCharsSecureHeadersDebugToolbar

备注

过滤器按配置文件中定义的顺序执行。但是,如果启用, DebugToolbar 总是最后执行,因为它应该能够捕获其他过滤器中发生的所有事情。

InvalidChars

此过滤器禁止用户输入数据($_GET$_POST$_COOKIEphp://input)包含以下字符:

  • 无效的 UTF-8 字符

  • 除换行和制表符之外的控制字符

SecureHeaders

此过滤器添加 HTTP 响应头,你的应用程序可以使用它们来提高应用程序的安全性。

如果要自定义头,请扩展 CodeIgniter\Filters\SecureHeaders 并覆盖 $headers 属性。并在 app/Config/Filters.php 中更改 $aliases 属性:

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Filters extends BaseConfig
{
    public array $aliases = [
        // ...
        'secureheaders' => \App\Filters\SecureHeaders::class,
    ];

    // ...
}

如果你想了解安全头,请参阅 OWASP 安全头项目