レート制限は、アプリやウェブサイトのリソースを過剰または不適切な使用から保護するために重要です。悪意のある行為の介入、ボットによる攻撃、または見過ごされた脆弱性など、理由は何であれ、リソースの不正使用はアプリケーションへの正当なアクセスを妨げ、深刻なリスクをもたらす可能性があります。

この記事では、LaravelアプリケーションのAPIにレート制限を追加する方法をご紹介します。

LaravelでAPIトラフィックを制限する

レート制限は、アプリケーションのリソースの搾取を緩和するために設計されたメカニズムです。多くの用途がありますが、特に大規模でスケーラブルなシステムのパブリックAPIに有用です。これにより、すべての正当なユーザーがシステムリソースに公平にアクセスできるようになります。

レート制限はまた、セキュリティ、コスト管理、システム全体の安定性にとっても重要です。分散サービス拒否(DDoS)攻撃などのリクエストベースの攻撃を防ぐのに役立ちます。この攻撃は、リクエストを繰り返し送信することで、アプリケーションやウェブサイトサーバーへのアクセスを圧倒し、妨害するものです。

レート制限を実装する方法はいくつかあります。要求者を特徴付ける変数を使用し、誰がどの程度の頻度でアプリケーションにアクセスできるかを決定できます。一般的な変数には、以下のようなものがあります。

  • IPアドレス:IPアドレスに基づいてレート制限を実装することで、アドレスごとのリクエスト数を制限できます。この方法は、ユーザーが認証情報を提供せずにアプリケーションにアクセスできる状況で特に有益です。
  • APIキー:APIキーを使ってアクセスを制限する場合、リクエスト送信者にAPIキーを提供し、キーごとにレート制限を設定します。この手法では、生成したAPIキーごとに別のアクセスレベルを適用することもできます。
  • クライアントID:ユーザーがAPIリクエストのヘッダーや本文に埋め込むことのできるクライアントIDを、事前に生成しておくこともできます。この方法では、クライアントがシステムリソースを独占できないように、IDごとにアクセスレベルを設定可能です。

Laravelミドルウェア

ミドルウェアは、アプリケーションに入るHTTPリクエストを検査し、フィルタリングする便利なメカニズムです。基本的には、アプリケーションとその基礎となるインフラストラクチャの間にあるコードの層で、リソース間の通信を可能にします。

レート制限の実装方法

今回の記事では、Laravel 10フレームワークの既存のミニライブラリAPIを使用して、Laravel Throttleを用いたデモを行います。サンプルのプロジェクトには、コレクション内のブックを管理するために必要な基本的な作成、読み取り、更新、削除(CRUD)と、レート制限のコンセプトをいくつか示すための2つのルートが含まれています。

前提条件

LaravelでのAPI開発の基本に精通していることを前提としています。以下のものがあることを確認してください。

  • PHP 8.2、ComposerLaravelがローカルマシンにインストールされ、設定されていること
  • 有効なKinstaアカウント
  • コードをプッシュするためのGitHubGitLab、またはBitbucketアカウント

また、MyKinstaを使用して、このAPIをセットアップしてデプロイします。プロジェクトテンプレートに沿って進み、こちらのソースコードから最終結果をプレビューすることができます。

Laravelアプリケーションのセットアップ

  1. 始めに、プロジェクトテンプレートを複製します。
  2. 次に、プロジェクトのルートディレクトリに.envファイルを作成し、その中に.env.exampleの内容をコピーします。
  3. 次に、以下のコマンドを使用してセットアップを完了し、アプリケーションの依存関係をインストールし、アプリキーを生成します。
composer install
php artisan key:generate

このコマンドでアプリキーが.envファイルに自動的に追加されない場合は、php artisan key:generate --showを実行し、生成されたキーをコピーして、APP_KEYの値として.envファイルに貼り付けます。

  1. 依存関係のインストールとアプリキーの生成が完了したら、以下のコマンドを使用してアプリケーションを起動します。
php artisan serve

このコマンドによりアプリケーションを起動し、ブラウザからhttps://127.0.0.1:8000にアクセスできます。

  1. URLにアクセスして、Laravelのウェルカムページが表示されることを確認してください。

The Laravel welcome page displays its logo at the top-center.
Laravelのウェルカム画面

データベースの設定

MyKinstaでアプリケーションのデータベースを設定しましょう。

  1. MyKinstaアカウントに移動し「サービスを追加」ボタンをクリックします。

The upper segment of the MyKinsta Dashboard tab features a top toolbar.
複数のサービスが設定された状態のMyKinstaの「ダッシュボード」画面

  1. サービスを追加」メニューから「データベース」をクリックし、データベースインスタンスを開始するためのパラメータを設定します。

Kinsta's
MyKinstaのデータベース設定

この説明ではMariaDBを使用しますが、Kinstaで利用可能なLaravelがサポートするデータベースオプションのいずれも選択できます。

  1. データベースの情報を入力したら、「続行」ボタンをクリックしてプロセスを確定します。

Kinstaでプロビジョニングしたデータベースには、内部接続パラメータと外部接続パラメータがあります。同じKinstaアカウント内でホストしているアプリケーションには内部接続を使用し、外部との接続には外部接続パラメータを使用します。ここでは、外部データベース認証情報を使用します。

  1. Laravelの.envで以下のコード(スクリーンショットにあるような実際の外部認証情報をコピーして貼り付け)を使用してください。

The Kinsta dashboard displays the
MyKinstaでデータベース設定情報を確認する

DB_CONNECTION=mysql
DB_HOST=your_host_name
DB_PORT=your_port
DB_DATABASE=your_database_info
DB_USERNAME=your_username
DB_PASSWORD=your_password
  1. データベースの認証情報を入力した後、以下のコマンドを使用しデータベースのマイグレーションを適用して接続をテストします。
php artisan migrate

すべてが正しく機能すれば、以下のような応答が表示されるはずです。

The terminal output displays the
ターミナル上でデータベースのマイグレーションに成功

  1. 次に、以下のコマンドを使用して、アプリケーションルートをリストアップし、すでに実装されているルートを確認します。
php artisan route:list

すると、以下のように利用可能なAPIエンドポイントが表示されます。

The terminal displays the
アプリケーションのルート一覧がターミナルに表示される

  1. アプリケーションを起動し、すべてが正常に動作することを確認してください。PostmanやCURLのようなツールを使って、ターミナルからエンドポイントをテストすることができます。

Laravelアプリケーションでレート制限する方法

Laravelアプリケーションには、複数のレート制限テクニックがあります。IPアドレスのセットをブロックしたり、ユーザーのIPアドレスやuser_idに基づいて期間ベースのリクエスト制限を実施したりできます。それでは、それぞれの方法を見ていきましょう。

  1. 以下のコマンドを使用して、Laravel Throttleパッケージをインストールします。
composer require "graham-campbell/throttle:^10.0"
  1. また、vendor configurationsファイルを公開することで、Laravel Throttleの設定を調整できます。
php artisan vendor:publish --provider="GrahamCampbellThrottleThrottleServiceProvider"

IPアドレスをブロックする方法

レート制限のテクニックの1つで、指定したIPアドレスのセットからのリクエストをブロックすることができます。

  1. 始めに、必要となるミドルウェアを作成します。
php artisan make:middleware RestrictMiddleware
  1. 次に、作成したapp/Http/Middleware/RestrictMiddleware.phpミドルウェアファイルを開き、handle関数のコードを以下に置き換えます。ファイルの一番上にあるインポートのリストにuse App;が追加されていることを確認してください。
$restrictedIps = ['127.0.0.1', '102.129.158.0'];
if(in_array($request->ip(), $restrictedIps)){
  App::abort(403, 'Request forbidden');
}
return $next($request);
  1. app/Http/Kernel.phpファイルで、middlewareAliasesの配列を以下のように変更して、このミドルウェアアプリのエイリアスを作成します。
    protected $middlewareAliases = [
    . . .
    'custom.restrict' => AppHttpMiddlewareRestrictMiddleware::class,
    ];
    1. 次に、このミドルウェアをroutes/api.phpファイルの/restricted-routeに適用しテストを行います。
    Route::middleware(['custom.restrict'])->group(function () {
      Route::get('/restricted-route', [BookController::class, 'getBooks']);
    });

    これが問題なく動作すると、ミドルウェアにより、$restrictedIpsの配列にあるIP(127.0.0.1102.129.158.0)からのすべてのリクエストがブロックされます。これらのIPからのリクエストに対しては、以下のように403 Forbiddenレスポンスが表示されます。

    The Postman app returns a
    Postmanの/restricted-route GETエンドポイントに対する403 Forbiddenレスポンス

    IPアドレスでリクエストに制限をかける方法

    次に、IPアドレスを使ってリクエストに制限を設ける方法です。

    1. routes/api.php/bookエンドポイントのGETPATCHルートにThrottleミドルウェアを適用します。
    Route::middleware(['throttle:minute'])->group(function () {
      Route::get('/book', [BookController::class, 'getBooks']);
    });
    
    Route::middleware(['throttle:5,1'])->group(function () {
      Route::patch('/book', [BookController::class, 'updateBook']);
    });
    1. また、app/Providers/RouteServiceProviderファイルのconfigureRateLimiting関数を、上記のルートに追加したミドルウェアの情報に変更します。
    … 
    RateLimiter::for('minute', function (Request $request) {
      return Limit::perMinute(5)->by($request->ip());
    });

    この設定により、/book GETエンドポイントへのリクエストは1分あたり5件に制限されます。

    The Postman app returns a
    Postmanの/book GETエンドポイントに対するレスポンス(429 Too Many Requests)

    ユーザーIDとセッションに基づきリクエストに制限をかける方法

    1. user_idsessionパラメータを使ってレート制限を行うには、app/Providers/RouteServiceProviderファイルのconfigureRateLimiting関数を、以下に変更します。
    ...
    RateLimiter::for('user', function (Request $request) {
      return Limit::perMinute(10)->by($request->user()?->id ?: $request->ip());
    });
    RateLimiter::for('session', function (Request $request) {
      return Limit::perMinute(15)->by($request->session()->get('key') ?: $request->ip());
    });
    1. 最後に、このコードをroutes/api.php ファイルの/book/{id} GET/book POSTルートに適用します。
    Route::middleware(['throttle:user'])->group(function () {
      Route::get('/book/{id}', [BookController::class, 'getBook']);
    });
    Route::middleware(['throttle:session'])->group(function () {
      Route::post('/book', [BookController::class, 'createBook']);
    });

    このコードはそれぞれ、user_idsessionを使ってリクエストを制限します。

    Throttleのその他のメソッド

    Laravel Throttleには、レート制限の実装を細かくコントロールするためのメソッドがいくつか用意されています。例えば以下の通りです。

    • attempt:エンドポイントをヒット、ヒットカウントをインクリメントし、設定されたヒット制限を超えたかどうかを示すブール値を返す
    • hit:Throttleをヒットし、ヒットカウントを増加させ、$thisを返し、別の(オプションの)メソッド呼び出しを有効にする
    • clear:Throttleカウントをゼロにリセットし、$thisを返す(必要に応じて別のメソッドを呼び出すことができる)
    • count:Throttleの総ヒット数を返す
    • check:Throttleのヒット制限を超えたかどうかを示すブール値を返す
    1. レート制限についてこれらのメソッドを使用するために、以下のコマンドを使用してCustomMiddlewareというミドルウェアアプリを作成してみます。
    php artisan make:middleware CustomMiddleware
    1. 次に、app/Http/Middleware/CustomMiddleware.phpに新しく作成したミドルウェアファイルに以下のインポートファイルを追加します。
    use GrahamCampbellThrottleFacadesThrottle;
    use App;
    1. 次に、handleメソッドの内容を以下のコードに置き換えます。
    $throttler = Throttle::get($request, 5, 1);
    Throttle::attempt($request);
    if(!$throttler->check()){
      App::abort(429, 'Too many requests');
    }
    return $next($request);
    1. app/Http/Kernel.phpファイルで、middlewareAliasesの配列を以下のように変更して、このミドルウェアアプリのエイリアスを作成します。
    protected $middlewareAliases = [
    . . .
    'custom.throttle' => AppHttpMiddlewareCustomMiddleware::class, 
    ];
    1. 次に、このミドルウェアをroutes/api.phpファイルの/custom-routeに適用します。
    Route::middleware(['custom.throttle'])->group(function () {
      Route::get('/custom-route', [BookController::class, 'getBooks']);
    });

    先ほど実装したカスタムミドルウェアは、スロットルリミットを超えたかどうかをcheckメソッドを使って確認します。制限を超えた場合、429エラーが返されます。そうでなければ、リクエストの続行を許可することになります。

    アプリケーションをKinstaにデプロイする方法

    Laravelアプリケーションにレート制限を実装する方法を理解したところで、今度はアプリをKinstaにデプロイして、一般にアクセスできる状態にしてみましょう。

    1. 用意したコードをGitHub、GitLab、またはBitbucketにプッシュします。
    2. Kinstaから、「サービスを追加」ボタンをクリックし、一覧から「アプリケーション」を選択します。GitアカウントをKinstaアカウントに紐付け、デプロイするリポジトリを選択します。
    3. 基本情報」で、アプリケーションに名前を付け、希望のデータセンターを選択します。また、必要なアプリケーション環境変数を追加してください。これはローカルの.envファイルに存在する変数に対応するものになります(APP_KEYとデータベース設定用の変数)。

    The
    アプリケーションデプロイに際しMyKinstaで設定を指定していく

    1. 続行」ボタンをクリックして、ビルド環境変数を選択します。必要なパラメータが自動で入力されるので、デフォルトの値のままで問題ありません。
    2. プロセス」タブでは、デフォルトの値のままにするか、プロセスの名前を入力できます。また、このタブでPodとインスタンスサイズを選択することもできます。
    3. 最後に、「お支払い」タブに選択した項目の概要が表示されます。ご希望のお支払い方法を追加し、プロセスの確定を行います。
    4. 完了したら、「アプリケーション」タブをクリックして、デプロイ済みアプリケーション一覧を表示します。
    5. アプリケーション名をクリックすると、以下のスクリーンショットのようにデプロイメントの詳細が表示されます。ここにあるアプリケーションのURLを使用してアクセスできます。

    The MyKinsta
    MyKinstaでデプロイが終わり詳しい情報が表示される

    アプリケーションのテスト方法

    1. アプリケーションをローカルでテストするには、php artisan serveコマンドを使用できます。

    このコマンドは、アプリケーションのブラウザをhttp://localhost:8000でアクセスできるようにするものです。レート制限機能をトリガーするために繰り返し呼び出すことで、ここからレート制限を実装したAPIエンドポイントをテストすることができます。

    KinstaサーバーによりAccess Forbiddenのレスポンスが表示される可能性があります。これは、アプリケーションの配信方法をKinstaに指示する設定を行っていないためです。以下の調整を行いましょう。

    1. アプリのルートディレクトリに.htaccessファイルを作成し、次のコードを記述します。
     <IfModule mod_rewrite.c>
      RewriteEngine On
      RewriteRule ^(.*)$ public/$1 [L]
    </IfModule>
    1. これをGitHubにプッシュすると、Kinstaにより自動で再デプロイが実行されます。
    2. 先ほどのURLを使ってアプリケーションを開き、Laravelのウェルカムページが表示されていることを確認してください。

    Postmanを使用してレート制限を実装したAPIエンドポイントを、設定した制限に達するまで繰り返しコールしてテストすることができます。制限を超えると、429 Too Many Requestsレスポンスが出されます。

    まとめ

    LaravelのAPIにレート制限機能を統合すると、ユーザーによるアプリケーションのリソース消費をうまく管理することができます。レート制限を行うことで、過不足なく信頼性の高いユーザーエクスペリエンスを提供可能です。また、アプリケーションの基礎となるインフラの機能と効率を確保する上でも有用でしょう。

    Kinstaでは数々の記事を執筆しています。Laravelはもちろんのこと、その他のウェブテクノロジーに関するコンテンツが盛りだくさんです。高品質かつお手頃なホスティングサービスは、アプリケーションを支える大事な土台です。Kinstaを是非ともご検討ください。

Marcia Ramos Kinsta

Kinstaのエディトリアルチームリード。大のオープンソース&コーディング好き。IT業界向けのテクニカルライティングと編集に7年以上携わり、的確かつ簡潔なコンテンツを制作しながら、チームで協力し合い、ワークフローの改善を行っている。