Pular para o conteúdo principal

Rate Limiting

Cette page documente la configuration du rate limiting pour proteger l'API contre les abus.

Vue d'ensemble

Le rate limiting limite le nombre de requetes qu'un client peut effectuer dans une periode donnee, protegeant contre :

  • Attaques par force brute
  • Deni de service (DoS)
  • Scraping abusif
  • Surcharge du serveur

Architecture

┌─────────────────┐ ┌─────────────────┐ ┌──────────────┐
│ Client │ Request │ Rate Limiter │ Allow │ Backend │
│ │ ------> │ (Redis) │ ------> │ API │
│ │ │ │ │ │
│ │ <------ │ │ │ │
│ │ 429 Too │ │ │ │
│ │ Many │ │ │ │
└─────────────────┘ └─────────────────┘ └──────────────┘

Configuration

Installation du bundle

# config/packages/rate_limiter.yaml
framework:
rate_limiter:
# Limite globale API
api_general:
policy: sliding_window
limit: 100
interval: '1 minute'

# Limite login (protection brute force)
api_login:
policy: fixed_window
limit: 5
interval: '1 minute'

# Limite par IP
api_per_ip:
policy: sliding_window
limit: 1000
interval: '1 hour'

# Limite export (ressources intensives)
api_export:
policy: fixed_window
limit: 10
interval: '1 hour'

Variables d'environnement

# Activation rate limiting
RATE_LIMITING_ENABLED=true

# Backend de stockage
RATE_LIMITER_STORAGE=redis

# Redis connection
REDIS_URL=redis://localhost:6379

Politiques

Fixed Window

Compte les requetes dans une fenetre fixe (ex: par minute).

|---1 min---|---1 min---|---1 min---|
| 100 req | 100 req | 100 req |

Avantage : Simple, predictible Inconvenient : Burst possible aux limites

Sliding Window

Fenetre glissante basee sur l'heure actuelle.

|--------1 min--------|
10:00:30 10:01:30

Avantage : Lissage du trafic Inconvenient : Plus de ressources

Token Bucket

Seau a jetons avec regeneration.

Avantage : Autorise bursts controles Inconvenient : Configuration complexe

Implementation

Controleur avec rate limiting

use Symfony\Component\RateLimiter\RateLimiterFactory;

class AuthController extends AbstractController
{
public function __construct(
private RateLimiterFactory $loginLimiter
) {}

#[Route('/api/auth/login', methods: ['POST'])]
public function login(Request $request): Response
{
$limiter = $this->loginLimiter->create($request->getClientIp());

if (!$limiter->consume()->isAccepted()) {
return $this->json([
'success' => false,
'error' => 'Trop de tentatives. Reessayez dans 1 minute.'
], 429);
}

// Logique de login...
}
}

Listener global

class RateLimitListener implements EventSubscriberInterface
{
public function onKernelRequest(RequestEvent $event): void
{
$request = $event->getRequest();
$limiter = $this->apiLimiter->create($request->getClientIp());

if (!$limiter->consume()->isAccepted()) {
$response = new JsonResponse([
'success' => false,
'error' => 'Rate limit exceeded',
'retry_after' => $limiter->getInfo()->getRetryAfter()->getTimestamp()
], 429);

$response->headers->set('Retry-After', $limiter->getInfo()->getRetryAfter()->getTimestamp());
$event->setResponse($response);
}
}
}

Headers HTTP

Reponse normale

HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 75
X-RateLimit-Reset: 1718198400

Limite atteinte

HTTP/1.1 429 Too Many Requests
Retry-After: 45
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1718198400

{
"success": false,
"error": "Rate limit exceeded",
"retry_after": 45
}

Limites par endpoint

EndpointLimiteIntervalleRaison
/api/auth/login51 minProtection brute force
/api/auth/magic-link35 minEviter spam email
/api/export/*101 heureRessources intensives
/api/* (global)1001 minProtection generale

Exemptions

Certains clients peuvent etre exemptes :

rate_limiter:
api_general:
# ...
exempt:
- 127.0.0.1
- '192.168.1.0/24' # Reseau interne

Par role

if ($this->isGranted('ROLE_API_UNLIMITED')) {
return; // Pas de limite
}

Monitoring

Metriques Prometheus

# Requetes limitees
rate_limit_exceeded_total{endpoint="/api/auth/login"} 42

# Requetes proches de la limite
rate_limit_near_threshold{endpoint="/api/*"} 156

Alertes Grafana

  • Alerte si > 100 requetes bloquees / minute
  • Alerte si meme IP bloquee > 10 fois

Stockage Redis

Configuration

services:
rate_limiter.storage.redis:
class: Symfony\Component\RateLimiter\Storage\CacheStorage
arguments:
- '@cache.rate_limiter'

framework:
cache:
pools:
cache.rate_limiter:
adapter: cache.adapter.redis
provider: 'redis://localhost:6379'

Avantages

  • Partage entre instances (cluster)
  • Persistance des compteurs
  • Performance elevee

Bonnes pratiques

:::tip Recommandations

  1. Limites adaptees : Ajustez selon l'usage reel
  2. Login strict : Limite basse pour les endpoints d'auth
  3. Headers clairs : Informez le client des limites
  4. Monitoring : Surveillez les blocages
  5. Exemptions limitees : N'exemptez que le necessaire :::

Troubleshooting

ProblemeCauseSolution
Blocages legitimesLimite trop basseAugmenter la limite
Redis downStockage indisponibleFallback en memoire
IP partageeNAT/ProxyUtiliser X-Forwarded-For