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
| Endpoint | Limite | Intervalle | Raison |
|---|---|---|---|
/api/auth/login | 5 | 1 min | Protection brute force |
/api/auth/magic-link | 3 | 5 min | Eviter spam email |
/api/export/* | 10 | 1 heure | Ressources intensives |
/api/* (global) | 100 | 1 min | Protection 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
- Limites adaptees : Ajustez selon l'usage reel
- Login strict : Limite basse pour les endpoints d'auth
- Headers clairs : Informez le client des limites
- Monitoring : Surveillez les blocages
- Exemptions limitees : N'exemptez que le necessaire :::
Troubleshooting
| Probleme | Cause | Solution |
|---|---|---|
| Blocages legitimes | Limite trop basse | Augmenter la limite |
| Redis down | Stockage indisponible | Fallback en memoire |
| IP partagee | NAT/Proxy | Utiliser X-Forwarded-For |