Баним пользователя за превышение частоты запросов

Разберем вариант логики скрипта, который позволяет заблокировать временно IP, с которого идет много запросов. Рассмотрим сценарий https://raw.githubusercontent.com/ArtNazarov/protector/main/protector.php .  

<?php
// scripts ban user for 
// frequently requests
define("LOGGER", false);
define("HOST_DIR", __DIR__);
define("MAX_REQ", 5);
define("TIMEOUT_EXPIRE", 60*10);


function ipf($ip){
 return HOST_DIR . '/ips/'. $ip.'.txt';
}

function save_blacklist($ip){
   $f_blacklist = fopen(HOST_DIR . '/blacklist.txt', 'a+');
	 fwrite($f_blacklist, $ip . "\n\r");
	 fclose($f_blacklist);
}

function save_ip_stat($ip, $items){
  if (LOGGER){
  echo "сохраняем файл статистики<br>";
  };
  $data = implode('|', $items);
  $fh = fopen(ipf($ip), 'w');
  fwrite($fh, $data);
  fclose($fh);
}

function get_ip_stat($ip){
  if (LOGGER){
    echo "считываем файл статистики<br>";
  };
    $data = file_get_contents(  ipf( $ip ));
    $items = explode('|', $data);
    return $items;
}

function get_ms(){
$curTime = microtime(true);
return $curTime;  
}

function get_ms_diff($nowTime, $t){
return ($nowTime - $t);
}

function get_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'];
};
  return $user_ip_address;
}

function exists_ip($ip){
 return file_exists( ipf($ip) );
}

function init_stat($ip){
   	$items = [ $ip, 1, get_ms() ]; 
    save_ip_stat($ip, $items);
}

function main(){

$ip = get_ip();
if (LOGGER){
echo "Запрос с адреса " . $ip . "<br/>";
};
if (!exists_ip($ip)){
  init_stat($ip);
}

if (exists_ip( $ip ) ){
  if (LOGGER){
  echo "файл найден";
  };
  $items = get_ip_stat($ip);
  
  $counter = $items[1];

   
  $time = $items[2];
  $nowTime = get_ms();
  
  
 
  


  if (LOGGER){
  echo "Опрошено раз " . $counter . "<br>";
  echo "Время " . $time . "<br>";
  echo "Сейчас " . $nowTime . "<br>";
  };
  
  $isStatOld = false;
  $ms_delay = get_ms_diff($nowTime, $time);
  //echo $ms_delay;
  if ($ms_delay>TIMEOUT_EXPIRE){
   if (LOGGER) { echo "Разница больше " . TIMEOUT_EXPIRE . "<br/>";};
		$isStatOld = true;
	};


  


  if (($counter>MAX_REQ) && (!$isStatOld))        {
	 echo "Временно заблокирован за превышение запросов. Доступ откроется через " . strval(floor((TIMEOUT_EXPIRE-$ms_delay)/60))." минут";
	 save_blacklist($ip);
   save_ip_stat($ip, [$ip,  $counter+1, $time]); 
   
	 exit(0);
      }
  else
      {
        
	      if ($isStatOld) {
          if (LOGGER) {echo "Статистика устарела"; };
          init_stat($ip);
      } else
            {
   save_ip_stat($ip, [$ip, $counter+1,     get_ms()]);
        };
      }
      
      }
}

main();
?>
    Идея состоит в том, чтобы отлавливать тех пользователей, которые превышают максимально допустимое количество запросов за заданное количество секунд. Допустим, мы хотим разрешить не более 5 запросов от одного IP на промежуток времени TIMEOUT_EXPIRE = 60*10 секунд, то есть 10 минут. В этом случае логика в следующем - будем для каждого ip создавать запись в файловой системе, которая содержит время самого первого обращения и счетчик запросов. Если файла нет, т.е. ip новый, то мы создадим новую запись, в которой счетчик будет иметь значение 1. Если файл есть, мы сравниваем максимальное количество запросов с текущим и выясняем, насколько устарела запись (т.е. превышено ли время TIMEOUT_EXPIRE). Если время превышено (бан прошел), то счетчик необходимо сбросить и пустить пользователя дальше. Если время не превышено, а количество запросов за серию обращений больше допустимого, то прерываем выполнение во входной точке (обычно index.php) и сообщаем, через какое время будет разбан ( оно равно в данном варианте TIMEOUT-задержка)/60 в минутах. Другой подход состоит в том, чтобы запретить запросы на основании слишком небольшого промежутка между двумя последними, что также свидетельствует о высокой частоте опроса (чем выше частота, тем меньше времени между последовательными обращениями) ( примером является https://github.com/ArtNazarov/PottoCMS/blob/master/classes/Core/Protector/Protector.library.php )

Теги документа