控制器过滤器

控制器过滤器允许你在控制器执行前后执行特定操作。与 事件 不同,你可以选择将过滤器应用于特定的 URI 或路由。前置过滤器可以修改请求(Request),而后置过滤器可以操作甚至修改响应(Response),这为开发者提供了极大的灵活性和控制力。

以下是使用过滤器实现的常见场景示例:

  • 对传入请求实施 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
    }
}

前置过滤器

替换请求对象

在任何前置过滤器中,你可以返回修改后的 $request 对象,该对象将替换当前请求,确保控制器执行时使用的是修改后的请求实例。

终止后续过滤器执行

当存在多个过滤器时,你可能需要在某个过滤器之后终止后续过滤器的执行。只需返回 任意非空值 即可实现。若前置过滤器返回空值,控制器操作及后续过滤器仍会继续执行。

需要注意的是,返回 Request 实例属于例外情况。在前置过滤器中返回该实例不会终止执行流程,仅会替换当前的 $request 对象。

返回响应对象

由于前置过滤器在控制器执行前运行,有时你可能希望阻止控制器的后续操作。这通常用于执行重定向,如下例所示:

<?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 库文档。

后置过滤器

后置过滤器与前置过滤器类似,但只能返回 $response 对象且无法终止脚本执行。这允许你修改最终输出内容或对其进行后续处理,例如确保安全头正确设置、缓存最终输出或实施敏感词过滤等。

配置过滤器

有两种方式配置过滤器的执行时机:一种在 app/Config/Filters.php 中配置,另一种在 app/Config/Routes.php 中配置。若需为定义的路由指定过滤器,请使用 app/Config/Routes.php 并参考 URI 路由 章节。

备注

最安全的过滤器应用方式是 禁用自动路由为路由设置过滤器

app/Config/Filters.php

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 = [
        'api-prep' => [
            \App\Filters\Negotiate::class,
            \App\Filters\ApiAuth::class,
        ],
    ];

    // ...
}

根据需求定义任意数量的别名。

$required

在 4.5.0 版本加入.

第二部分用于定义 必选过滤器。这些特殊过滤器会应用于框架处理的每个请求,且在其他类型过滤器之前和之后执行。

备注

必选过滤器始终执行。但如果路由不存在,仅会执行前置过滤器。

需谨慎设置此处过滤器的数量,过多会影响请求处理性能。默认提供的必选过滤器支撑框架核心功能,移除可能导致相关功能失效。详见 内置过滤器

通过将别名添加至 beforeafter 数组来指定必选过滤器:

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Filters extends BaseConfig
{
    // ...
    public array $required = [
        'before' => [
            'forcehttps', // Force Global Secure Requests
            'pagecache',  // Web Page Caching
        ],
        'after' => [
            'pagecache',   // Web Page Caching
            'performance', // Performance Metrics
            'toolbar',     // Debug Toolbar
        ],
    ];

    // ...
}

$globals

第三部分用于定义应用于所有有效请求的全局过滤器。需注意过滤器数量对性能的影响。

通过将别名添加至 beforeafter 数组来指定全局过滤器:

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Filters extends BaseConfig
{
    // ...

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

    // ...
}
排除特定 URI

有时需要对绝大多数请求应用过滤器,但排除少数例外。例如在 CSRF 保护中排除第三方网站可访问的特定 URI。

在别名旁添加包含 except 键和 URI 路径(相对于 BaseURL)的数组实现:

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Filters extends BaseConfig
{
    // ...

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

    // ...
}

警告

在 v4.4.7 之前,由于漏洞,过滤器处理的 URI 路径未进行 URL 解码。即路由中的 URI 路径与过滤器配置的路径可能不一致。详见 控制器过滤器中的路径

在过滤器设置中,可使用正则表达式或通配符(*)匹配 URI 路径。上例中,所有以 api/ 开头的路径将豁免 CSRF 保护。

如需指定多个路径,可使用路径模式数组:

<?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' => ['invalidchars', 'csrf'],
        'GET'  => ['csrf'],
    ];

    // ...
}

备注

$globals$filters 不同,这些过滤器仅作为前置过滤器运行。

除标准 HTTP 方法外,还支持特殊值 CLI,该值应用于所有命令行请求。

备注

v4.5.0 之前版本需使用 小写 指定 HTTP 方法名称。

$filters

该属性是过滤器别名数组。每个别名可指定 beforeafter 数组,包含过滤器应应用的 URI 路径模式(相对于 BaseURL):

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Filters extends BaseConfig
{
    // ...

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

    // ...
}

警告

在 v4.4.7 之前,由于漏洞,过滤器处理的 URI 路径未进行 URL 解码。详见 控制器过滤器中的路径

过滤器参数

在 4.4.0 版本加入.

配置 $filters 时,可向过滤器传递额外参数:

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Filters extends BaseConfig
{
    // ...

    public $filters = [
        'group:admin,superadmin'  => ['before' => ['admin/*']],
        'permission:users.manage' => ['before' => ['admin/users/*']],
    ];

    // ...
}

本例中,当 URI 匹配 admin/* 时,数组 ['admin', 'superadmin'] 将作为 $arguments 传递给 group 过滤器的 before() 方法;当匹配 admin/users/* 时,数组 ['users.manage'] 将传递给 permission 过滤器的 before() 方法。

备注

v4.6.0 之前版本,同一过滤器无法使用不同参数多次运行。

过滤器执行顺序

重要

自 v4.5.0 起,过滤器执行顺序已变更。如需保持旧版顺序,请将 Config\Feature::$oldFilterOrder 设为 true

过滤器按以下顺序执行:

  • 前置过滤器:必选 → 全局 → 方法 → 过滤器 → 路由

  • 后置过滤器:路由 → 过滤器 → 全局 → 必选

备注

必选 过滤器自 v4.5.0 起可用。

备注

v4.5.0 之前版本,路由过滤器(在 app/Config/Routes.php 中定义)优先于 app/Config/Filters.php 中的过滤器执行,且后置过滤器的执行顺序未反转。详见 升级指南

验证过滤器配置

CodeIgniter 提供以下 命令 用于检查路由的过滤器配置。

filter:check

在 4.3.0 版本加入.

示例:检查 / 路由的 GET 方法过滤器配置:

php spark filter:check get /

输出结果如下:

+--------+-------+----------------------+-------------------------------+
| Method | Route | Before Filters       | After Filters                 |
+--------+-------+----------------------+-------------------------------+
| GET    | /     | forcehttps pagecache | pagecache performance toolbar |
+--------+-------+----------------------+-------------------------------+

Before Filter Classes:
CodeIgniter\Filters\ForceHTTPS → CodeIgniter\Filters\PageCache
After Filter Classes:
CodeIgniter\Filters\PageCache → CodeIgniter\Filters\PerformanceMetrics → CodeIgniter\Filters\DebugToolbar

备注

自 v4.6.0 起,输出表格中会显示过滤器参数,并展示实际使用的过滤器类名。

也可通过 spark routes 命令查看路由和过滤器,但使用正则表达式定义路由时可能显示不准确。详见 URI 路由

内置过滤器

CodeIgniter4 内置以下过滤器:

备注

过滤器按配置文件中的顺序执行。但若启用,DebugToolbar 始终最后执行,以便捕获其他过滤器的所有操作。

ForceHTTPS

在 4.5.0 版本加入.

该过滤器提供「强制全局安全请求」功能。若设置 Config\App:$forceGlobalSecureRequests 为 true,所有请求必须通过 HTTPS 连接。若请求不安全,用户将被重定向至安全页面,并设置 HSTS 头。

PerformanceMetrics

在 4.5.0 版本加入.

该过滤器提供性能指标伪变量。若需在视图中显示从框架启动到输出生成的总耗时,使用:

{elapsed_time}

显示内存使用量则使用:

{memory_usage}

若不需要此功能,从 $required['after'] 中移除 'performance'

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 安全头项目