Реализация ограничения попыток входа (Brute-Force Protection) на сайте
Атаки перебора паролей — один из самых распространённых векторов взлома аккаунтов. Без ограничений злоумышленник может проверять тысячи паролей в минуту через автоматизированные скрипты.
Стратегии защиты
Защита от брутфорса строится из нескольких уровней:
- Rate limiting — ограничение числа попыток за период
- Account lockout — временная блокировка аккаунта
- IP-блокировка — блокировка источника атаки
- Progressive delays — нарастающие задержки между попытками
- CAPTCHA — после N неудачных попыток
Реализация на Laravel
// app/Http/Controllers/Auth/LoginController.php
class LoginController extends Controller
{
protected int $maxAttempts = 5;
protected int $decayMinutes = 15;
public function login(Request $request)
{
// Встроенный throttle Laravel использует кэш (Redis/database)
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if (Auth::attempt($request->only('email', 'password'))) {
$this->clearLoginAttempts($request);
return redirect()->intended('/dashboard');
}
$this->incrementLoginAttempts($request);
return back()->withErrors(['email' => 'Неверные учётные данные']);
}
}
Трейт ThrottlesLogins использует ключ вида email|IP — блокирует конкретную связку, не весь IP.
Кастомная реализация через Redis
class BruteForceProtection
{
private Redis $redis;
private int $maxAttempts = 5;
private int $lockoutSeconds = 900; // 15 минут
private int $windowSeconds = 300; // 5 минут
public function attempt(string $key): bool
{
$redisKey = "login_attempts:{$key}";
$count = $this->redis->incr($redisKey);
if ($count === 1) {
$this->redis->expire($redisKey, $this->windowSeconds);
}
if ($count > $this->maxAttempts) {
$this->redis->setex("lockout:{$key}", $this->lockoutSeconds, 1);
return false;
}
return true;
}
public function isLocked(string $key): bool
{
return (bool) $this->redis->exists("lockout:{$key}");
}
public function getLockoutTtl(string $key): int
{
return $this->redis->ttl("lockout:{$key}");
}
public function reset(string $key): void
{
$this->redis->del("login_attempts:{$key}", "lockout:{$key}");
}
}
Progressive Delay (нарастающие задержки)
Вместо полной блокировки — увеличение задержки с каждой неудачной попыткой:
// Задержки: 1с → 2с → 4с → 8с → 16с → 32с (максимум)
$delay = min(pow(2, $attempts - 1), 32);
sleep($delay);
Это замедляет автоматический перебор без полного отключения функции входа.
Разграничение блокировок
Важно разделять уровни блокировки:
| Уровень | Ключ | Условие | Срок |
|---|---|---|---|
| По email | login:email:[email protected] |
5 попыток за 5 мин | 15 мин |
| По IP | login:ip:1.2.3.4 |
20 попыток за 5 мин | 30 мин |
| Глобальный | login:global |
1000 попыток за 1 мин | Alert |
Блокировка только по IP может навредить пользователям за NAT/прокси. Блокировка только по email — легко обойти с другого IP.
CAPTCHA после нескольких неудачных попыток
// В контроллере
$attempts = $this->limiter->attempts($throttleKey);
$showCaptcha = $attempts >= 3;
return view('auth.login', compact('showCaptcha'));
@if ($showCaptcha)
<div class="cf-turnstile" data-sitekey="{{ config('services.turnstile.site_key') }}"></div>
@endif
Уведомление пользователя
При успешном входе после нескольких неудачных попыток — уведомление на email:
// После успешной аутентификации
if ($previousFailedAttempts > 2) {
Mail::to($user)->queue(new SuspiciousLoginNotification($request->ip()));
}
Мониторинг
Логировать все неудачные попытки в structured format:
Log::warning('Failed login attempt', [
'email' => $request->email,
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
'timestamp' => now()->toIso8601String(),
]);
Алерт в Grafana/Datadog при всплеске failed login events — признак активной атаки.
Срок реализации
- Базовый throttle Laravel: 1 день
- Кастомная Redis-реализация с прогрессивными задержками: 2–3 дня
- Интеграция с мониторингом и уведомлениями: +1 день







