こんにちは!アプリケーション開発やインフラの保守に日々奮闘している新人のIT担当者の皆さん、セキュリティの世界へようこそ。
今回は、WebサービスやAPIを守るための非常に重要な盾である「APIレート制限(Rate Limiting)」と、それを使った「DoS攻撃対策」についてお話しします。
「API?レート制限?なんだか難しそう……」と思われるかもしれませんが、安心してくださいね。身近な例えを交えながら、一歩ずつ優しく紐解いていきましょう!
—
1. 家の鍵と泥棒に例えるAPIのセキュリティ
突然ですが、ご自宅の玄関を思い浮かべてみてください。普通の家には頑丈な鍵がついていますよね。でも、もしその鍵が「ガチャガチャと1秒間に100回試せる自動ドア」だったらどうでしょうか?
悪い人がやってきて、秒速で何万通りもの鍵のパターンを試し、あっという間に家の中に侵入されてしまいますよね。これが、セキュリティの世界でいう「ブルートフォース攻撃(総当たり攻撃)」や、サーバーをパンクさせる「DoS(Denial of Service)攻撃」の仕組みです。
API(アプリケーション・プログラミング・インターフェース)もこれと全く同じです。
外部のシステムやユーザーがあなたの作ったAPIを叩くとき、もし「何回叩いても怒られない状態」にしておくと、悪意あるプログラム(ボット)が無限にリクエストを送り続け、サーバーのCPUやメモリを食い潰してしまいます。結果として、本当にサービスを必要としている一般のお客様が使えなくなってしまうのです。
だからこそ、「1人のお客さんが短時間に許されるアクセス回数」をきちんと制限する、すなわちAPIレート制限が必要になるわけですね。
—
2. トークンバケットアルゴリズムってなに?
レート制限を実装するための代表的な仕組みに、「トークンバケット(Token Bucket)アルゴリズム」というものがあります。名前を聞くと難しそうですが、仕組みはとてもユニークで面白いですよ。
カフェのスタンプカードや、ゲームの「スタミナ回復」をイメージしてください。
1. バケツ(制限枠)がある:ユーザーごとに「トークン(許可証)」を入れるバケツが用意されています。
2. 一定のペースでトークンが補充される:例えば「1秒に1枚」のペースで、バケツに新しいトークンがポチャンと追加されます。ただし、バケツの大きさに上限(例: 10枚まで)があり、溢れた分は捨てられます。
3. APIを使うとトークンが減る:ユーザーがAPIを1回叩くごとに、バケツからトークンを1枚消費します。
4. 空っぽになるとお預け:バケツのトークンが0枚の時にAPIを叩こうとすると、「今はダメですよ、ちょっと待ってね!」とエラー(HTTPステータスコード 429 Too Many Requests)が返されます。
この仕組みの素晴らしいところは、「普段のちょっとした連打やバースト(急激なアクセス増)には耐えられるけれど、機械的な猛烈な連打はしっかり弾ける」という点です。人間が普通に操作している範囲ならスムーズに動き、ボットの異常な高速アクセスだけを綺麗にシャットアウトできる優れものです。
—
3. どうやって制限する?(IPベース vs ユーザーベース)
レート制限をかけるとき、誰を基準にして「バケツ」を分けるべきでしょうか? 主に2つのアプローチがあります。
① IPアドレスベースの制限
アクセスしてきた端末のネット上の住所(IPアドレス)ごとに制限をかけます。
- メリット:ログインしていなくても(未認証のユーザーでも)制限をかけられるため、ログイン画面への総当たり攻撃などに有効です。
- デメリット:オフィスやカフェなど、同じルーター(同じIPアドレス)を使っている複数の人が、全員で1つのバケツを共有することになります。そのため、「社内の他の人のせいで自分までAPIが使えなくなった!」という巻き込み事故が起きることがあります。
② ユーザーベース(トークン/APIキー)の制限
会員登録してもらい、発行したIDやAPIキーごとに制限をかけます。
- メリット:ユーザー個別にしっかり管理できるため、公平性が保たれます。
- デメリット:アカウントを大量に作られると(いわゆる「自作自演アカウント」)、IPを分散させながら攻撃されると防ぎにくくなります。
実際の現場では、この2つを組み合わせたり、エンドポイントの重要度に応じて使い分けたりするのが一般的です。
—
4. 実装のイメージと防衛ヘッダーの読み方
それでは、実際にPHPを使った簡単なAPIレート制限のイメージコードを見てみましょう。今回は直感的に分かりやすいよう、簡易的なコードに日本語のコメントを添えて解説します。
<?php
// 例: シンプルなIPベースのトークンバケット風レート制限のイメージ
// 実際の開発では Redis などの高速なインメモリDBを使用しますが、
// ここでは分かりやすくセッションやキャッシュを使った概念コードにします。
$client_ip = $_SERVER['REMOTE_ADDR']; // アクセスしてきた人のIPアドレスを取得
$cache_key = "rate_limit_" . $client_ip;
// 現在のアクセス回数を取得(なければ0)
$current_requests = apcu_fetch($cache_key) ?: 0;
$max_limit = 60; // 1分間に許可する最大リクエスト数
$time_window = 60; // 制限時間(秒)
if ($current_requests >= $max_limit) {
// 制限を超えた場合の処理
// HTTPステータス 429 (Too Many Requests) を返却する
header('HTTP/1.1 429 Too Many Requests');
// クライアントに「いつまで待てばいいか」を伝える親切なヘッダー
header('Retry-After: 60');
echo json_encode([
'error' => 'リクエストの制限回数を超えました。しばらく待ってから再度お試しください。'
]);
exit;
}
// リクエスト回数を1つ増やして保存(有効期限も設定)
apcu_store($cache_key, $current_requests + 1, $time_window);
// --- ここから通常のAPI処理 ---
echo json_encode([
'message' => 'APIへのアクセスが成功しました!',
'remaining' => $max_limit - ($current_requests + 1) // 残りの許容回数を親切に返す
]);
?>
💡 現場で役立つレスポンスヘッダーのポイント
APIを設計・利用する際、サーバー側から以下のようなヘッダーを返してあげると、クライアント側の開発者が非常に喜んでくれます。
X-RateLimit-Limit:そのユーザーが一定期間内に使える全体の枠(例: 60)X-RateLimit-Remaining:今、あと何回使える残高があるか(例: 45)X-RateLimit-Reset:制限枠がリセットされるタイミング(Unixタイムスタンプなど)Retry-After:制限を超えた時に、「あと何秒待てばいいか」を示す秒数
これらをちゃんと返してあげることで、クライアントアプリ側も「あ、今は少しアクセスを控えよう」と賢く制御できるようになります。
—
5. おわりに:一歩ずつ、強固なシステムへ
APIレート制限とDoS攻撃対策、いかがでしたでしょうか?
「家の鍵をただ頑丈にするだけでなく、ガチャガチャ何度も試す不審者を自動で見つけて一時的に門前払いする仕組み」のイメージが湧いたのではないでしょうか。
実際の現場では、Nginxなどのリバースプロキシ層、WAF(Webアプリケーションファイアウォール)、あるいはAPI Gateway(AWS API GatewayやCloudflareなど)の機能をうまく活用して、アプリケーション本体に負荷をかけ手前でブロックするのが現代の主流です。
最初は覚えることが多くて大変に感じるかもしれませんが、一つひとつの仕組みは私たちの日常生活の防犯と地続きです。「どうすれば不正なアクセスを防ぎつつ、本当のお客様を快適に迎えられるか」という視点を大切にしながら、ぜひ日々の開発やインフラ構築に活かしてみてくださいね。
それでは、次のセキュリティの学び舎でお会いしましょう!
コメント