节流器

Throttler 类提供了一种非常简单的方法来限制在设定的时间段内执行的活动次数。这最常用于对 API 执行速率限制,或限制用户对表单进行的尝试次数,以帮助防止暴力攻击。该类本身可以用于任何需要根据设定时间间隔内的操作进行节流的场景。

概述

Throttler 实现了一个简化的 令牌桶 算法版本。这基本上将您想要的每个操作都视为一个桶。当您调用 check() 方法时,您告诉它桶的大小,它可以容纳多少个令牌以及时间间隔。默认情况下,每个 check() 调用使用 1 个可用令牌。让我们通过一个例子来阐明这一点。

假设我们希望一个动作每秒执行一次。第一次调用 Throttler 将如下所示。第一个参数是桶的名称,第二个参数是桶中容纳的令牌数量,第三个参数是桶重新填充所需的时间。

<?php

$throttler = \Config\Services::throttler();
$throttler->check($name, 60, MINUTE);

这里我们使用了一个 全局常量 来表示时间,使其更易读。这意味着桶每分钟允许 60 个动作,或者每秒 1 个动作。

假设一个第三方脚本试图反复访问某个 URL。最初,它可以在不到一秒的时间内使用所有 60 个令牌。但是,之后 Throttler 仅允许每秒执行一个动作,这可能会减缓请求速度,使攻击不再值得。

注意

为了使 Throttler 类正常工作,必须将 Cache 库设置为使用除 dummy 之外的处理程序。为了获得最佳性能,建议使用内存缓存,例如 Redis 或 Memcached。

速率限制

Throttler 类本身不执行任何速率限制或请求节流,但它是实现速率限制的关键。提供了一个示例 过滤器,它实现了每秒每 IP 地址一个请求的非常简单的速率限制。这里我们将介绍它的工作原理,以及如何在应用程序中设置和使用它。

代码

您可以在 app/Filters/Throttle.php 中创建自己的 Throttler 过滤器,如下所示

<?php

namespace App\Filters;

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

class Throttle implements FilterInterface
{
    /**
     * This is a demo implementation of using the Throttler class
     * to implement rate limiting for your application.
     *
     * @param array|null $arguments
     *
     * @return mixed
     */
    public function before(RequestInterface $request, $arguments = null)
    {
        $throttler = Services::throttler();

        // Restrict an IP address to no more than 1 request
        // per second across the entire site.
        if ($throttler->check(md5($request->getIPAddress()), 60, MINUTE) === false) {
            return Services::response()->setStatusCode(429);
        }
    }

    /**
     * We don't have anything to do here.
     *
     * @param array|null $arguments
     *
     * @return mixed
     */
    public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
    {
        // ...
    }
}

运行此方法时,首先会获取节流器实例。接下来,它使用 IP 地址作为桶名称,并将设置限制为每秒一个请求。如果节流器拒绝检查,返回 false,则返回一个状态码为 429 - 太多尝试的响应,并且脚本执行在到达控制器之前结束。此示例将根据单个 IP 地址对所有对网站发出的请求进行节流,而不是每个页面。

应用过滤器

我们不一定需要对网站上的每个页面进行节流。对于许多 Web 应用程序来说,这最有意义的是仅对 POST 请求应用,尽管 API 可能希望限制用户发出的每个请求。为了将此应用于传入请求,您需要编辑 **app/Config/Filters.php** 并首先为过滤器添加别名

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

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

    // ...
}

接下来,我们将它分配给网站上发出的所有 POST 请求

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Filters extends BaseConfig
{
    public $methods = [
        'post' => ['throttle'],
    ];

    // ...
}

警告

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

就是这样。现在,网站上发出的所有 POST 请求都必须进行速率限制。

类参考

check(string $key, int $capacity, int $seconds[, int $cost = 1])
参数:
  • $key (string) – 桶的名称

  • $capacity (int) – 桶容纳的令牌数量

  • $seconds (int) – 桶完全填满所需的时间(秒)

  • $cost (int) – 此操作消耗的令牌数量

返回值:

如果可以执行操作,则返回 true,否则返回 false

返回类型:

bool

检查桶中是否还有剩余的令牌,或者在分配的时间限制内是否使用了太多令牌。在每次检查期间,如果成功,可用令牌将减少 $cost。

getTokentime()
返回值:

距离下一个令牌可用还有多少秒。

返回类型:

整数

check() 运行并返回 false 后,可以使用此方法确定下一个令牌可用之前的时间,并可以再次尝试该操作。在这种情况下,强制执行的最小等待时间为一秒。

remove(string $key) self
参数:
  • $key (string) – 桶的名称

返回值:

$this

返回类型:

self

删除并重置桶。如果桶不存在,不会失败。