特定のページだけに連続アクセスを遮断する機構をつける必要性があったので実装
外部のAPIをリクエストするページをつくったが、誰かが連打するとAPIに自分のサーバがDDoS攻撃したとされそうなので連続アクセスを遮断する機構を作った。
注意
サーバに対するDDoS攻撃とかはiptablesとかでやるべき
以下の実装はファイル書き込みの読み書きを行うので大量のアクセスがあると重たくなる
あとデフォのままつかうとテンポラリディレクトリをかなり汚す
方法
- 任意の場所(デフォルトだとテンポラリ)にアクセスしてきたIP名のファイルを作る
- IP名のファイルにUnixtimeを打刻
- IP名のファイルを読み取り、過去に打刻したUnixtimeがあれば比較する
- 比較し結果連続アクセスと判断した場合遮断する
<?php
//アクセス記録を残すPath(テンポラリ/IP)を決定
$file = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'apache' . DIRECTORY_SEPARATOR . $_SERVER['REMOTE_ADDR'];
//アクセスログを記録
//ファイル名はIPで中身はunixtime
//厳密に書き込むならロック制御が必要(ロックされてるとE_NOTICEかE_WARNINGがでるハズ)
file_put_contents($file, time() . PHP_EOL, FILE_APPEND | LOCK_EX);
//読み取り間隔を設定
//lineSizeはunixtime10桁+改行固定(11桁になるのは2286年)
$lineSize = 11;
//読み取り行数
$maxRow = 5;
//連続アクセスしたとするしきい値(秒)
$limitTime = 30;
//読み取るバイト数
$readByte = $lineSize * $maxRow;
//バイト数分後ろから読む
$readContent = file_get_contents($file, false, null, filesize($file) - $readByte);
$lines = explode(PHP_EOL, $readContent);
//バイト単位で読むので先頭がかけてる場合を想定して予め先頭行を切り落とす
array_shift($lines);
//初回と連続アクセス規制に満たなければ通過させる
if ($lines[0] + $limitTime < time() || count($lines) < $maxRow) {
echo 'おっけー';
exit;
}
echo $lines[0] - (time() - $limitTime) . '秒まってね';
exit;
/* 以下バグ
//設定時間より未来の時間だったら遮断する
if ($lines[0] > time() - $limitTime){
echo $lines[0] - (time() - $limitTime) .'秒まってね';
exit;
}
echo 'おっけー';
exit;
*/
連続アクセスの調整
実装とは異なるがほぼ以下のように読み替えることができると思う
//1分間に10回あったら制限する場合
$maxRow = 10;
$limitTime = 60;
誤ってるところがあれば報告してくださると助かります