スロットラー

Throttler クラスは、特定のアクティビティを一定期間内に特定の回数に制限するための非常に簡単な方法を提供します。これは、API のレート制限を実行したり、ユーザーがフォームに対して行える試行回数を制限してブルートフォース攻撃を防ぐために最もよく使用されます。クラス自体は、設定された時間間隔内でのアクションに基づいてスロットルする必要があるあらゆるものに使用できます。

概要

スロットラーは、トークンバケットアルゴリズムの簡略版を実装しています。これは基本的に、必要な各アクションをバケットとして扱います。 `check()` メソッドを呼び出すと、バケットのサイズ、保持できるトークンの数、時間間隔を指定します。各 `check()` 呼び出しは、デフォルトで利用可能なトークンの 1 つを使用します。例を見てみましょう。

アクションを 1 秒に 1 回実行したいとしましょう。スロットラーへの最初の呼び出しは次のようになります。最初のパラメーターはバケット名、2 番目のパラメーターはバケットが保持するトークンの数、3 番目はバケットが補充されるまでの時間です。

<?php

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

ここでは、グローバル定数 の 1 つを時間に使用して、少し読みやすくしています。これは、バケットが 1 分間に 60 アクション、つまり 1 秒に 1 アクションを許可することを示しています。

サードパーティのスクリプトが URL に繰り返しアクセスしようとしているとしましょう。最初は、1 秒未満ですべての 60 トークンを使用できます。ただし、その後、スロットラーは 1 秒間に 1 つのアクションのみを許可するため、リクエストが十分に遅くなり、攻撃がもはや価値がなくなる可能性があります。

注意

スロットラークラスを動作させるには、キャッシュライブラリをダミー以外のハンドラーを使用するように設定する必要があります。パフォーマンスを最大限に高めるには、Redis や Memcached などのインメモリキャッシュをお勧めします。

レート制限

Throttler クラス自体はレート制限やリクエストスロットリングを行いませんが、それを機能させるための鍵となります。IP アドレスごとに 1 秒間に 1 リクエストの非常に簡単なレート制限を実装する フィルター の例が提供されています。ここでは、その仕組みと、アプリケーションで設定して使用を開始する方法について説明します。

コード

**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 アドレスをバケット名として使用し、1 秒間に 1 リクエストに制限するように設定します。スロットラーがチェックを拒否して false を返すと、ステータスコードが 429 - Too Many Attempts に設定されたレスポンスが返され、スクリプトの実行はコントローラーに到達する前に終了します。この例では、ページごとではなく、サイトに対して行われたすべてのリクエストで単一の 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 (これはおそらくコード例の一部であり、文脈によっては「自身」と訳すべきです)

バケットを削除&リセットします。バケットが存在しない場合でもエラーは発生しません。