<?php
namespace App\EventSubscriber;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* SÉCURITÉ : limite les tentatives de connexion (anti force brute).
* Maximum 15 appels aux routes de connexion par adresse IP et par fenêtre de 15 minutes.
* L'adresse IP vient uniquement de REMOTE_ADDR (les en-têtes X-Forwarded-For sont falsifiables).
*/
class LoginThrottleSubscriber implements EventSubscriberInterface
{
public const MAX_ATTEMPTS = 15;
public const WINDOW_SECONDS = 900;
private const LOGIN_ROUTES = ['login_user', 'verif_cnx', 'verif_cnx1'];
private CacheItemPoolInterface $cache;
public function __construct(CacheItemPoolInterface $cache)
{
$this->cache = $cache;
}
public static function getSubscribedEvents(): array
{
return [KernelEvents::REQUEST => ['onKernelRequest', 8]];
}
public function onKernelRequest(RequestEvent $event): void
{
if (!$event->isMainRequest()) {
return;
}
$request = $event->getRequest();
if (!\in_array($request->attributes->get('_route'), self::LOGIN_ROUTES, true)) {
return;
}
$ip = (string) $request->server->get('REMOTE_ADDR', 'inconnue');
try {
$item = $this->cache->getItem('login_throttle_' . md5($ip));
$now = time();
$data = $item->isHit() ? $item->get() : ['count' => 0, 'start' => $now];
if (!\is_array($data) || $now - ($data['start'] ?? 0) > self::WINDOW_SECONDS) {
$data = ['count' => 0, 'start' => $now];
}
$data['count']++;
$item->set($data)->expiresAfter(self::WINDOW_SECONDS);
$this->cache->save($item);
} catch (\Throwable $e) {
return; // en cas de panne du cache, ne pas bloquer les connexions légitimes
}
if ($data['count'] > self::MAX_ATTEMPTS) {
$reste = max(1, self::WINDOW_SECONDS - (time() - $data['start']));
$event->setResponse(new Response(
'Trop de tentatives de connexion. Réessayez dans quelques minutes.',
429,
['Retry-After' => (string) $reste, 'Content-Type' => 'text/plain; charset=UTF-8']
));
}
}
}