Ubuntu20.04でRedisを使用してPHPレート制限を実装する方法
序章
Redis( Re mote Di ctionary S erver)は、メモリ内のオープンソースソフトウェアです。 これは、サーバーのRAMを使用するデータ構造ストアであり、最速のソリッドステートドライブ(SSD)よりも数倍高速です。 これにより、Redisの応答性が高くなるため、レート制限に適しています。
レート制限は、ユーザーがサーバーにリソースを要求できる回数に上限を設けるテクノロジーです。 多くのサービスは、ユーザーがサーバーに過度の負荷をかけようとしたときにサービスが悪用されるのを防ぐために、レート制限を実装しています。
たとえば、 PHP を使用してWebアプリケーションにパブリックAPI(アプリケーションプログラミングインターフェイス)を実装する場合、何らかの形式のレート制限が必要です。 その理由は、APIを公開するときに、アプリケーションユーザーが特定の時間枠でアクションを繰り返すことができる回数を制御したいからです。 制御がないと、ユーザーはシステムを完全に停止させる可能性があります。
特定の制限を超えるユーザーの要求を拒否すると、アプリケーションをスムーズに実行できます。 顧客が多い場合、レート制限により、各顧客がアプリケーションに高速でアクセスできるようにする公正な使用ポリシーが適用されます。 レート制限は、帯域幅のコストを削減し、サーバーの輻輳を最小限に抑えるのにも役立ちます。
MySQLのようなデータベースにユーザーアクティビティを記録することにより、レート制限モジュールをコーディングすることは実用的かもしれません。 ただし、データをディスクからフェッチして設定された制限と比較する必要があるため、多くのユーザーがシステムにアクセスする場合、最終製品はスケーラブルではない可能性があります。 これは遅いだけでなく、リレーショナルデータベース管理システムはこの目的のために設計されていません。
Redisはインメモリデータベースとして機能するため、レートリミッターを作成するための適格な候補であり、この目的で信頼できることが証明されています。
このチュートリアルでは、Ubuntu20.04サーバーでRedisを使用してレート制限するためのPHPスクリプトを実装します。
前提条件
始める前に、次のものが必要です。
-
Ubuntu20.04サーバーとsudo権限を持つroot以外のユーザー。 Ubuntu 20.04を使用したサーバーの初期設定ガイドを参照して、サーバーを設定し、新しいユーザーを作成してください。
-
LAMPスタック。 Ubuntu 20.04 にLinux、Apache、MySQL、PHP(LAMP)スタックをインストールする方法に従ってください。 このガイドでは、ステップ4 — Webサイトの仮想ホストの作成をスキップして、Apacheのインストールで作成済みのデフォルトの仮想ホストを使用できます。
-
Redisサーバー。 Ubuntu 20.04にRedisをインストールして保護する方法-クイックスタートチュートリアルに従って、これを設定します。
ステップ1—PHP用のRedisライブラリをインストールする
まず、Ubuntuサーバーパッケージリポジトリインデックスを更新することから始めます。 次に、をインストールします php-redis
拡大。 これは、PHPコードにRedisを実装できるようにするライブラリです。 これを行うには、次のコマンドを実行します。
- sudo apt update
- sudo apt install -y php-redis
次に、Apacheサーバーを再起動してロードします php-redis
図書館:
- sudo systemctl restart apache2
ソフトウェア情報インデックスを更新し、PHP用のRedisライブラリをインストールしたら、IPアドレスに基づいてユーザーのアクセスを制限するPHPリソースを作成します。
ステップ2—レート制限のためのPHPWebリソースの構築
このステップでは、 test.php
ルートディレクトリ内のファイル(/var/www/html/
)Webサーバーの。 このファイルは一般に公開され、ユーザーはWebブラウザにそのアドレスを入力して実行できます。 ただし、このガイドの基礎として、後でリソースへのアクセスをテストします。 curl
指図。
サンプルリソースファイルを使用すると、ユーザーは10秒の時間枠で3回アクセスできます。 制限を超えようとすると、レート制限されていることを通知するエラーが発生します。
このファイルのコア機能は、Redisサーバーに大きく依存しています。 ユーザーが初めてリソースをリクエストすると、ファイル内のPHPコードは、ユーザーのIPアドレスに基づいてRedisサーバー上にキーを作成します。
ユーザーがリソースに再度アクセスすると、PHPコードはユーザーのIPアドレスをRedisサーバーに保存されているキーと照合し、キーが存在する場合は値を1つインクリメントしようとします。 PHPコードは、増分された値が設定された最大制限に達しているかどうかをチェックし続けます。
ユーザーのIPアドレスに基づくRedisキーは、10秒後に期限切れになります。 この期間が経過すると、ユーザーのWebリソースへのアクセスのログ記録が再開されます。
開始するには、 /var/www/html/test.php
ファイル:
- sudo nano /var/www/html/test.php
次に、次の情報を入力してRedisクラスを初期化します。 に適切な値を入力することを忘れないでください REDIS_PASSWORD
:
<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('REDIS_PASSWORD');
$redis->auth
Redisサーバーにプレーンテキスト認証を実装します。 これは、ローカルで作業している間は問題ありません( localhost
)。ただし、リモートRedisサーバーを使用している場合は、SSL認証の使用を検討してください。
次に、同じファイルで、次の変数を初期化します。
. . .
$max_calls_limit = 3;
$time_period = 10;
$total_user_calls = 0;
あなたが定義した:
$max_calls_limit
:は、ユーザーがリソースにアクセスできる呼び出しの最大数です。$time_period
:ユーザーがリソースにアクセスできる時間枠を秒単位で定義します。$max_calls_limit
.$total_user_calls
:指定された時間枠内にユーザーがリソースへのアクセスを要求した回数を取得する変数を初期化します。
次に、次のコードを追加して、Webリソースを要求しているユーザーのIPアドレスを取得します。
. . .
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
$user_ip_address = $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$user_ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
$user_ip_address = $_SERVER['REMOTE_ADDR'];
}
このコードはデモンストレーションの目的でユーザーのIPアドレスを使用しますが、認証が必要な保護されたリソースがサーバー上にある場合は、ユーザー名またはアクセストークンを使用してユーザーのアクティビティをログに記録できます。
このようなシナリオでは、システムに認証されたすべてのユーザーが一意の識別子(たとえば、顧客ID、開発者ID、ベンダーID、さらにはユーザーID)を持ちます。 (これを構成する場合は、の代わりにこれらの識別子を使用することを忘れないでください $user_ip_address
.)
このガイドでは、ユーザーIPアドレスで概念を証明できます。 したがって、前のコードスニペットでユーザーのIPアドレスを取得したら、次のコードブロックをファイルに追加します。
. . .
if (!$redis->exists($user_ip_address)) {
$redis->set($user_ip_address, 1);
$redis->expire($user_ip_address, $time_period);
$total_user_calls = 1;
} else {
$redis->INCR($user_ip_address);
$total_user_calls = $redis->get($user_ip_address);
if ($total_user_calls > $max_calls_limit) {
echo "User " . $user_ip_address . " limit exceeded.";
exit();
}
}
echo "Welcome " . $user_ip_address . " total calls made " . $total_user_calls . " in " . $time_period . " seconds";
このコードでは、 if...else
RedisサーバーにIPアドレスで定義されたキーがあるかどうかを確認するステートメント。 キーが存在しない場合は、 if (!$redis->exists($user_ip_address)) {...}
、それを設定し、その値を次のように定義します 1
コードを使用する $redis->set($user_ip_address, 1);
.
The $redis->expire($user_ip_address, $time_period);
期間内に期限切れになるようにキーを設定します。この場合は、 10
秒。
ユーザーのIPアドレスがRedisキーとして存在しない場合は、変数を設定します $total_user_calls
に 1
.
の中に ...else {...}...
ステートメントブロック、あなたは $redis->INCR($user_ip_address);
各IPアドレスキーに設定されたRedisキーの値を次のようにインクリメントするコマンド 1
. これは、キーがRedisサーバーにすでに設定されており、リピートリクエストとしてカウントされる場合にのみ発生します。
ステートメント $total_user_calls = $redis->get($user_ip_address);
RedisサーバーでIPアドレスベースのキーを確認することにより、ユーザーが行うリクエストの総数を取得します。
ファイルの終わりに向かって、あなたはを使用します ...if ($total_user_calls > $max_calls_limit) {... }..
制限を超えているかどうかを確認するステートメント。 その場合、ユーザーに次のように警告します echo "User " . $user_ip_address . " limit exceeded.";
. 最後に、ユーザーがその期間に行った訪問について、 echo "Welcome " . $user_ip_address . " total calls made " . $total_user_calls . " in " . $time_period . " seconds";
声明。
すべてのコードを追加した後、 /var/www/html/test.php
ファイルは次のようになります。
<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('REDIS_PASSWORD');
$max_calls_limit = 3;
$time_period = 10;
$total_user_calls = 0;
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
$user_ip_address = $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$user_ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
$user_ip_address = $_SERVER['REMOTE_ADDR'];
}
if (!$redis->exists($user_ip_address)) {
$redis->set($user_ip_address, 1);
$redis->expire($user_ip_address, $time_period);
$total_user_calls = 1;
} else {
$redis->INCR($user_ip_address);
$total_user_calls = $redis->get($user_ip_address);
if ($total_user_calls > $max_calls_limit) {
echo "User " . $user_ip_address . " limit exceeded.";
exit();
}
}
echo "Welcome " . $user_ip_address . " total calls made " . $total_user_calls . " in " . $time_period . " seconds";
編集が終了したら /var/www/html/test.php
ファイルを保存して閉じます。
これで、ユーザーのレート制限に必要なロジックをコーディングできました。 test.php
Webリソース。 次のステップでは、スクリプトをテストします。
ステップ3—Redisレート制限のテスト
このステップでは、 curl
ステップ2でコーディングしたWebリソースを要求するコマンド。 スクリプトを完全にチェックするには、1つのコマンドで5回リソースをリクエストします。 これを行うには、最後にプレースホルダーURLパラメーターを含めます。 test.php
ファイル。 ここでは、値を使用します ?[1-5]
実行するリクエストの最後に curl
5回コマンドします。
次のコマンドを実行します。
- curl -H "Accept: text/plain" -H "Content-Type: text/plain" -X GET http://localhost/test.php?[1-5]
コードを実行すると、次のような出力が表示されます。
Output[1/5]: http://localhost/test.php?1 --> <stdout>
--_curl_--http://localhost/test.php?1
Welcome 127.0.0.1 total calls made 1 in 10 seconds
[2/5]: http://localhost/test.php?2 --> <stdout>
--_curl_--http://localhost/test.php?2
Welcome 127.0.0.1 total calls made 2 in 10 seconds
[3/5]: http://localhost/test.php?3 --> <stdout>
--_curl_--http://localhost/test.php?3
Welcome 127.0.0.1 total calls made 3 in 10 seconds
[4/5]: http://localhost/test.php?4 --> <stdout>
--_curl_--http://localhost/test.php?4
User 127.0.0.1 limit exceeded.
[5/5]: http://localhost/test.php?5 --> <stdout>
--_curl_--http://localhost/test.php?5
User 127.0.0.1 limit exceeded.
お気づきのとおり、最初の3つのリクエストは問題なく実行されました。 ただし、スクリプトでは4番目と5番目のリクエストのレートが制限されています。 これは、Redisサーバーがユーザーのリクエストをレート制限していることを確認します。
このガイドでは、次の2つの変数に低い値を設定しました。
...
$max_calls_limit = 3;
$time_period = 10;
...
実稼働環境でアプリケーションを設計する場合、ユーザーがアプリケーションにアクセスする頻度に応じて、より高い値を検討できます。
これらの値を設定する前に、リアルタイムの統計を確認することをお勧めします。 たとえば、サーバーログに、平均的なユーザーが60秒ごとに1,000回アプリケーションにアクセスしたことが示されている場合は、それらの値をユーザーを制限するためのベンチマークとして使用できます。
より良い視点で物事を置くために、ここにレート制限の実装のいくつかの実際の例があります(2021年現在):
- Twitterは、1日あたり100,000件のリクエストのみを許可します
/statuses/mentions_timeline
と/statuses/user_timeline
エンドポイント。 - DigitalOceanは、OAuthトークンで認証されたユーザーごとに、APIエンドポイントで1時間あたり5,000リクエストを許可します。
- Googleカスタム検索JSONAPIでは、1日あたり100件の検索クエリを無料で利用できます。
結論
このチュートリアルでは、Ubuntu 20.04サーバーでRedisを使用してレート制限するためのPHPスクリプトを実装し、Webアプリケーションが不注意または悪意のある乱用から保護されるようにしました。 ユースケースに応じて、ニーズに合わせてコードを拡張できます。
本番環境で使用するためにApacheサーバーを保護することをお勧めします。 Ubuntu20.04でLet’sEncryptを使用してApacheを保護する方法チュートリアルに従ってください。
また、Redisがデータベースキャッシュとしてどのように機能するかを読むことも検討してください。 Ubuntu20.04チュートリアルでPHPを使用してMySQLのキャッシュとしてRedisを設定する方法を試してください。