# Security 类

Security 类提供用于防范跨站请求伪造（CSRF）攻击的方法。

## 加载类

如仅需通过加载该库来处理 CSRF 防护，则无需手动加载， 因其以过滤器形式运行，无需手动交互。

如确有直接访问需求，可通过 Services 文件加载：

    <?php

    $security = service('security');

## 跨站请求伪造（CSRF）

警告

CSRF 防护仅对 POST/PUT/PATCH/DELETE 请求有效。 其他方法的请求不受保护。

### 前提条件

使用 CodeIgniter 的 CSRF 防护时，仍需按以下方式编码， 否则 CSRF 防护可能被绕过。

#### 自动路由禁用时

执行以下操作之一：

1. 不使用 `$routes->add()`，路由中使用 HTTP 方法。

2. 处理前在控制器方法中检查请求方法。

示例:

    if (! $this->request->is('post')) {
        return $this->response->setStatusCode(405)->setBody('Method Not Allowed');
    }

备注

$this->request->is() 方法自 v4.3.0 起可用。 早期版本需使用 `if (strtolower($this->request->getMethod()) !== 'post')`。

#### 自动路由启用时

1. 处理前在控制器方法中检查请求方法。

示例:

    if (! $this->request->is('post')) {
        return $this->response->setStatusCode(405)->setBody('Method Not Allowed');
    }

### CSRF 配置

#### CSRF 防护方法

警告

如使用 Session，请务必使用基于 Session 的 CSRF 防护。基于 Cookie 的 CSRF 防护无法防止同站攻击。 详情请参见 GHSA-5hm8-vh6r-2cjq 。

默认使用基于 Cookie 的 CSRF 防护，即 OWASP 跨站请求伪造防护备忘单中的 双重提交 Cookie 方案。

也可使用基于 Session 的 CSRF 防护，即 同步器令牌模式 。

编辑 app/Config/Security.php 中的以下配置参数值，可设置为使用基于 Session 的 CSRF 防护：

    <?php

    namespace Config;

    use CodeIgniter\Config\BaseConfig;

    class Security extends BaseConfig
    {
        public $csrfProtection = 'session';

        // ...
    }

#### 令牌随机化

为缓解 BREACH 等压缩旁道攻击，并防止攻击者猜测 CSRF 令牌，可配置令牌随机化（默认关闭）。

启用后，将为令牌添加随机掩码并对其混淆。

编辑 app/Config/Security.php 中的以下配置参数值可启用：

    <?php

    namespace Config;

    use CodeIgniter\Config\BaseConfig;

    class Security extends BaseConfig
    {
        public $tokenRandomize = true;

        // ...
    }

#### 令牌重新生成

令牌可在每次提交时重新生成（默认），或 在 Session 或 CSRF Cookie 的整个生命周期内保持不变。

默认的令牌重新生成提供更强的安全性，但可能导致 可用性问题，因为其他令牌会失效（后退/前进 导航、多个标签页/窗口、异步操作等）。编辑 app/Config/Security.php 中的以下配置参数值可更改此行为：

    <?php

    namespace Config;

    use CodeIgniter\Config\BaseConfig;

    class Security extends BaseConfig
    {
        public $regenerate = true;

        // ...
    }

警告

如使用基于 Cookie 的 CSRF 防护，并在提交后使用 `redirect()`， 必须调用 `withCookie()` 发送重新生成的 CSRF Cookie。详情请参见 重定向。

备注

自 v4.2.3 起，可使用 `Security::generateHash()` 方法手动重新生成 CSRF 令牌。

#### 失败时重定向

自 v4.5.0 起，当请求未通过 CSRF 验证检查时，默认情况下， 生产环境中用户将被重定向到上一页，其他环境中将抛出 SecurityException。

备注

生产环境中使用 HTML 表单时，建议 启用此重定向以获得更好的用户体验。

升级用户应检查其配置文件。

如希望重定向到上一页，在 app/Config/Security.php 中将以下配置参数值设为 `true`：

    <?php

    namespace Config;

    use CodeIgniter\Config\BaseConfig;

    class Security extends BaseConfig
    {
        // ...

        public bool $redirect = true;

        // ...
    }

重定向时，会设置 `error` 闪存消息，可在视图中通过以下代码显示给最终用户:

    <?= session()->getFlashdata('error') ?>

这比直接崩溃提供了更好的体验。

即使 redirect 值为 `true`，AJAX 调用也不会重定向，而是抛出 SecurityException。

### 启用 CSRF 防护

修改 app/Config/Filters.php 并全局启用 csrf 过滤器，即可启用 CSRF 防护：

    <?php

    namespace Config;

    use CodeIgniter\Config\BaseConfig;

    class Filters extends BaseConfig
    {
        public $globals = [
            'before' => [
                // 'honeypot',
                'csrf',
            ],
        ];

        // ...
    }

特定 URI 可从 CSRF 防护中排除（例如接收外部 POST 内容的 API 端点）。可在过滤器中将其添加为例外：

    <?php

    namespace Config;

    use CodeIgniter\Config\BaseConfig;

    class Filters extends BaseConfig
    {
        public $globals = [
            'before' => [
                'csrf' => ['except' => ['api/record/save']],
            ],
        ];

        // ...
    }

也支持正则表达式（不区分大小写）：

    <?php

    namespace Config;

    use CodeIgniter\Config\BaseConfig;

    class Filters extends BaseConfig
    {
        public $globals = [
            'before' => [
                'csrf' => ['except' => ['api/record/[0-9]+']],
            ],
        ];

        // ...
    }

也可仅为特定方法启用 CSRF 过滤器：

    <?php

    namespace Config;

    use CodeIgniter\Config\BaseConfig;

    class Filters extends BaseConfig
    {
        public $methods = [
            'GET'  => ['csrf'],
            'POST' => ['csrf'],
        ];

        // ...
    }

警告

如使用 `$methods` 过滤器，应 禁用自动路由（传统版）， 因为 自动路由（传统版） 允许任何 HTTP 方法访问控制器。 以意外的方法访问控制器可能绕过过滤器。

### HTML 表单

如使用 表单辅助函数， `form_open()` 会自动在表单中插入隐藏的 csrf 字段。

备注

要使用 CSRF 字段自动生成功能，需要为表单页面开启 CSRF 过滤器。 大多数情况下该页面使用 `GET` 方法请求。

如未使用表单辅助函数，可使用始终可用的 `csrf_token()` 和 `csrf_hash()` 函数:

    <input type="hidden" name="<?= csrf_token() ?>" value="<?= csrf_hash() ?>" />

此外，可使用 `csrf_field()` 方法生成此隐藏 input 字段:

    // 生成：<input type="hidden" name="{csrf_token}" value="{csrf_hash}" />
    <?= csrf_field() ?>

发送 JSON 请求时，CSRF 令牌也可作为参数之一传递。 传递 CSRF 令牌的另一种方式是使用特殊 HTTP 标头，其名称可通过 `csrf_header()` 函数获取。

此外，可使用 `csrf_meta()` 方法生成 meta 标签:

    // 生成：<meta name="{csrf_header}" content="{csrf_hash}" />
    <?= csrf_meta() ?>

### 用户发送令牌的顺序

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

1. `$_POST` 数组

2. HTTP 标头

3. `php://input` （JSON 请求）— 注意此方式最慢，因为需要解码 JSON 然后重新编码

4. `php://input` （原始 body）— 用于 PUT、PATCH 和 DELETE 类型请求

备注

自 v4.4.2 起，会检查 `php://input` （原始 body）。

## 其他实用方法

大部分 Security 类方法无需直接使用。以下是一些与 CSRF 防护无关但可能有用的方法。

### sanitizeFilename()

尝试清理文件名以防止目录遍历尝试和其他安全威胁， 对通过用户输入提供的文件特别有用。第一个参数为要清理的路径。

如允许用户输入包含相对路径，例如 file/in/some/approved/folder.txt，可将第二个可选参数 `$relativePath` 设为 `true`。

    <?php

    $path = $security->sanitizeFilename($request->getVar('filepath'));

此方法是 Security 辅助函数中 `sanitize_filename()` 函数的别名。