限速器

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

概览

限速器实现了 令牌桶 算法的简化版本。这基本上将你想要的每个操作视为一个桶。当你调用 check() 方法时,你告诉它桶的大小,它可以容纳的令牌数和时间间隔。默认情况下,每个 check() 调用会使用可用令牌中的1个。让我们通过一个示例来说明这一点。

假设我们想要每秒钟发生一次操作。首次调用限速器将如下所示。第一个参数是桶的名称,第二个参数是桶容纳的令牌数,第三个是桶重新填充所需的时间量:

<?php

$throttler = service('throttler');
$throttler->check($name, 60, MINUTE);

这里我们使用 全局常量 之一来表示时间,使其更具可读性。这表示该桶允许每分钟60次操作,或每秒1次操作。

假设第三方脚本尝试重复点击一个 URL。起初,它将能够在不到1秒钟内使用这60个令牌。然而,在那之后,限速器只允许每秒执行一个操作,可能会减慢请求的速度,以至于攻击不再值得。

备注

要使限速器类起作用,必须将缓存库设置为使用 dummy 之外的处理程序。 为获得最佳性能,建议使用内存缓存,如 Redis 或 Memcached。

速率限制

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

代码

你可以在 app/Filters/Throttle.php 中创建自己的限速器过滤器,如下所示:

<?php

namespace App\Filters;

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

class Throttle implements FilterInterface
{
    /**
     * This is a demo implementation of using the Throttler class
     * to implement rate limiting for your application.
     *
     * @param list<string>|null $arguments
     *
     * @return ResponseInterface|void
     */
    public function before(RequestInterface $request, $arguments = null)
    {
        $throttler = service('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 service('response')->setStatusCode(429);
        }
    }

    /**
     * We don't have anything to do here.
     *
     * @param list<string>|null $arguments
     *
     * @return void
     */
    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 后,可以使用此方法来确定新的令牌应该可用并可以再次尝试该操作的时间。在这种情况下,强制等待时间最小为1秒。

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

返回:

$this

返回类型:

self

移除并重置桶。如果桶不存在也不会失败。