app/Customize/EventListener/IpRateLimitListener.php line 38

Open in your IDE?
  1. <?php
  2. namespace Customize\EventListener;
  3. use Symfony\Component\HttpKernel\Event\RequestEvent;
  4. use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
  5. use Symfony\Component\HttpKernel\KernelEvents;
  6. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  7. class IpRateLimitListener implements EventSubscriberInterface
  8. {
  9.     private $cacheDir;
  10.     
  11.     // ホワイトリストIP(自分のIPアドレスを追加)
  12.     private $whitelist = [
  13.         '127.0.0.1',
  14.         '::1',
  15.         // 自分のIPアドレスをここに追加
  16.         // 例: '123.456.789.012',
  17.     ];
  18.     
  19.     public function __construct(string $cacheDir)
  20.     {
  21.         $this->cacheDir $cacheDir '/ip_limit';
  22.         if (!is_dir($this->cacheDir)) {
  23.             mkdir($this->cacheDir0777true);
  24.         }
  25.     }
  26.     
  27.     public static function getSubscribedEvents(): array
  28.     {
  29.         return [
  30.             KernelEvents::REQUEST => ['onKernelRequest'15],
  31.         ];
  32.     }
  33.     
  34.     public function onKernelRequest(RequestEvent $event)
  35.     {
  36.         if (!$event->isMasterRequest()) {
  37.             return;
  38.         }
  39.         
  40.         $request $event->getRequest();
  41.         $ip $request->getClientIp();
  42.         
  43.         // ホワイトリストIPは除外
  44.         if (in_array($ip$this->whitelist)) {
  45.             return;
  46.         }
  47.         
  48.         // 管理画面は除外
  49.         if (strpos($request->getPathInfo(), '/admin') === 0) {
  50.             return;
  51.         }
  52.         
  53.         // ログインセッションチェック
  54.         $session $request->getSession();
  55.         if ($session) {
  56.             // 管理者ログイン中は除外
  57.             if ($session->has('_security_admin')) {
  58.                 return;
  59.             }
  60.             // 顧客ログイン中も除外(オプション)
  61.             // if ($session->has('_security_customer')) {
  62.             //     return;
  63.             // }
  64.         }
  65.         
  66.         // POSTリクエストのみチェック
  67.         if ($request->getMethod() !== 'POST') {
  68.             return;
  69.         }
  70.         
  71.         // 3段階の制限
  72.         // 1. 短期: 5分間に10回
  73.         $this->checkLimit($ip'short'10300);
  74.         
  75.         // 2. 中期: 1時間に30回
  76.         $this->checkLimit($ip'medium'303600);
  77.         
  78.         // 3. 長期: 24時間に100回(これを超えたら1日ブロック)
  79.         $this->checkLimit($ip'long'10086400);
  80.     }
  81.     
  82.     private function checkLimit($ip$type$maxAttempts$period)
  83.     {
  84.         $key md5($ip '_' $type);
  85.         $file $this->cacheDir '/' $key;
  86.         
  87.         $now time();
  88.         
  89.         if (file_exists($file)) {
  90.             $data json_decode(file_get_contents($file), true);
  91.             
  92.             // 古いデータをクリーンアップ
  93.             $data['attempts'] = array_filter($data['attempts'], function($time) use ($now$period) {
  94.                 return ($now $time) < $period;
  95.             });
  96.             
  97.             if (count($data['attempts']) >= $maxAttempts) {
  98.                 $oldest min($data['attempts']);
  99.                 $waitTime $period - ($now $oldest);
  100.                 
  101.                 // 長期制限に引っかかった場合は特別なログ
  102.                 if ($type === 'long') {
  103.                     error_log(sprintf(
  104.                         '[IP Rate Limit SEVERE] IP: %s blocked for 24 hours. Total attempts: %d',
  105.                         $ipcount($data['attempts'])
  106.                     ));
  107.                     
  108.                     throw new TooManyRequestsHttpException(
  109.                         $waitTime,
  110.                         '送信回数の上限を大幅に超えました。24時間後に再度お試しください。'
  111.                     );
  112.                 }
  113.                 
  114.                 error_log(sprintf(
  115.                     '[IP Rate Limit] IP: %s, Type: %s, Path: %s, Blocked for %d seconds',
  116.                     $ip$type$_SERVER['REQUEST_URI'] ?? 'unknown'$waitTime
  117.                 ));
  118.                 
  119.                 throw new TooManyRequestsHttpException(
  120.                     $waitTime,
  121.                     sprintf('送信回数が多すぎます。%d秒後に再度お試しください。'$waitTime)
  122.                 );
  123.             }
  124.             
  125.             $data['attempts'][] = $now;
  126.         } else {
  127.             $data = [
  128.                 'ip' => $ip,
  129.                 'attempts' => [$now],
  130.                 'first_seen' => date('Y-m-d H:i:s')
  131.             ];
  132.         }
  133.         
  134.         file_put_contents($filejson_encode($data));
  135.     }
  136. }