Rate Limiting dans les API : Protection Contre les Abus
Le rate limiting est une technique essentielle pour protéger les API contre les abus, la consommation excessive de ressources et les attaques par déni de service. Une implémentation adéquate garantit la disponibilité pour les utilisateurs légitimes tout en bloquant les comportements malveillants ou excessifs.
Pourquoi Implémenter le Rate Limiting ?
- Protection Contre le DDoS : Atténuer les attaques par déni de service
- Empêcher le Scraping : Compliquer l'extraction automatisée de données
- Maîtriser les Coûts : Éviter la consommation excessive de ressources de calcul
- Garantir la Qualité de Service : Répartir les ressources de manière équitable
- Empêcher le Brute Force : Limiter les tentatives d'authentification
Algorithmes de Rate Limiting
1. Token Bucket
Algorithme qui maintient un « seau » de tokens qui se remplissent au fil du temps :
class TokenBucket {
constructor(capacity, refillRate) {
this.capacity = capacity; // Capacidade máxima do balde
this.tokens = capacity; // Tokens disponíveis
this.refillRate = refillRate; // Tokens por segundo
this.lastRefill = Date.now();
}
tryConsume(tokens = 1) {
this.refill();
if (this.tokens >= tokens) {
this.tokens -= tokens;
return true; // Requisição permitida
}
return false; // Rate limit excedido
}
refill() {
const now = Date.now();
const timePassed = (now - this.lastRefill) / 1000;
const tokensToAdd = timePassed * this.refillRate;
this.tokens = Math.min(this.capacity, this.tokens + tokensToAdd);
this.lastRefill = now;
}
}
// Uso: 100 requisições máximo, recarrega 10/segundo
const bucket = new TokenBucket(100, 10);
Avantages : Permet des bursts contrôlés, lisse le trafic
Inconvénients : Plus complexe à implémenter
2. Leaky Bucket
Traite les requêtes à un débit constant, comme de l'eau qui s'écoule d'un seau :
- Les requêtes entrent dans le seau
- Traitées à un débit fixe
- L'excédent déborde (rejeté)
- Garantit une sortie uniforme
3. Fixed Window
Compte les requêtes dans des fenêtres de temps fixes :
class FixedWindowRateLimiter {
constructor(maxRequests, windowMs) {
this.maxRequests = maxRequests;
this.windowMs = windowMs;
this.requests = new Map();
}
isAllowed(userId) {
const now = Date.now();
const windowStart = Math.floor(now / this.windowMs) * this.windowMs;
const key = \`\$:\$\`;
const count = this.requests.get(key) || 0;
if (count < this.maxRequests) {
this.requests.set(key, count + 1);
return true;
}
return false;
}
}
// 100 requisições por hora
const limiter = new FixedWindowRateLimiter(100, 60 * 60 * 1000);
Problème : Permet jusqu'à 2x la limite aux bords des fenêtres
4. Sliding Window Log
Maintient un journal des horodatages des requêtes :
- Stocke un horodatage pour chaque requête
- Supprime les requêtes hors de la fenêtre
- Plus précis que Fixed Window
- Consommation de mémoire plus élevée
5. Sliding Window Counter
Combine Fixed Window avec un lissage :
// Calcula uma média ponderada entre janelas atual e anterior
const currentWindowCount = getCurrentWindowCount(userId);
const previousWindowCount = getPreviousWindowCount(userId);
const percentageInCurrentWindow = (now - currentWindowStart) / windowSize;
const estimatedCount =
previousWindowCount * (1 - percentageInCurrentWindow) +
currentWindowCount;
return estimatedCount < maxRequests;
Implémentation Pratique
Avec Redis (Recommandé en Production)
import Redis from 'ioredis';
const redis = new Redis();
async function checkRateLimit(userId, maxRequests = 100, windowSeconds = 60) {
const key = \`rate_limit:\$\`;
const now = Date.now();
const windowStart = now - (windowSeconds * 1000);
// Remover requisições antigas
await redis.zremrangebyscore(key, 0, windowStart);
// Contar requisições na janela
const requestCount = await redis.zcard(key);
if (requestCount < maxRequests) {
// Adicionar nova requisição
await redis.zadd(key, now, \`\$-\${Math.random()}\`);
await redis.expire(key, windowSeconds);
return { allowed: true, remaining: maxRequests - requestCount - 1 };
}
return { allowed: false, remaining: 0 };
}
// Middleware Express
app.use(async (req, res, next) => {
const userId = req.user?.id || req.ip;
const result = await checkRateLimit(userId);
res.set({
'X-RateLimit-Limit': 100,
'X-RateLimit-Remaining': result.remaining,
'X-RateLimit-Reset': new Date(Date.now() + 60000).toISOString()
});
if (!result.allowed) {
return res.status(429).json({
error: 'Too Many Requests',
retryAfter: 60
});
}
next();
});
Bibliothèques Populaires
- express-rate-limit : Middleware pour Express.js
- rate-limiter-flexible : Prend en charge plusieurs backends (Redis, Memcached, MySQL)
- Kong Rate Limiting : Plugin pour API Gateway
- AWS API Gateway : Rate limiting natif
Stratégies Avancées
Rate Limiting Hiérarchique
- Global : Limite totale de l'API (ex. : 1M req/min)
- Par Utilisateur : Limite individuelle (ex. : 1000 req/min)
- Par Endpoint : Limites spécifiques (login : 5 req/min)
- Par IP : Protection supplémentaire contre les abus
Rate Limiting Dynamique
- Ajuster les limites en fonction de la charge du système
- Augmenter les limites pour les utilisateurs premium
- Réduire les limites pendant les incidents
Whitelisting et Blacklisting
- Exempter les IP/utilisateurs de confiance
- Bloquer définitivement les attaquants connus
- Implémenter un système de réputation
Meilleures Pratiques
- Retourner des en-têtes informatifs (X-RateLimit-*)
- Utiliser le statut HTTP 429 (Too Many Requests)
- Inclure un en-tête Retry-After
- Documenter clairement les limites dans l'API
- Implémenter un backoff exponentiel côté client
- Surveiller les métriques de rate limiting
- Alerter sur les schémas anormaux
- Tester les limites avant la production
Outils de Surveillance
- Grafana + Prometheus : Visualiser les métriques de rate limiting
- Datadog : Surveillance et alertes
- CloudWatch : Pour les API sur AWS
- New Relic : APM avec prise en charge du rate limiting
