Rate Limiting in APIs: Schutz vor Missbrauch
Rate Limiting ist eine grundlegende Technik, um APIs vor Missbrauch, übermäßigem Ressourcenverbrauch und Denial-of-Service-Angriffen zu schützen. Eine angemessene Implementierung gewährleistet die Verfügbarkeit für legitime Benutzer und blockiert gleichzeitig bösartiges oder übermäßiges Verhalten.
Warum Rate Limiting Implementieren?
- Schutz vor DDoS: Denial-of-Service-Angriffe abwehren
- Scraping Verhindern: Automatisierte Datenextraktion erschweren
- Kosten Kontrollieren: Übermäßigen Verbrauch von Rechenressourcen vermeiden
- Dienstqualität Sicherstellen: Ressourcen gerecht verteilen
- Brute Force Verhindern: Authentifizierungsversuche begrenzen
Rate-Limiting-Algorithmen
1. Token Bucket
Ein Algorithmus, der einen „Eimer" mit Tokens unterhält, die sich im Laufe der Zeit wieder auffüllen:
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);
Vorteile: Ermöglicht kontrollierte Bursts, glättet den Datenverkehr
Nachteile: Komplexer zu implementieren
2. Leaky Bucket
Verarbeitet Anfragen mit konstanter Rate, wie Wasser, das aus einem Eimer ausläuft:
- Anfragen gelangen in den Eimer
- Mit fester Rate verarbeitet
- Überschuss läuft über (abgelehnt)
- Gewährleistet eine gleichmäßige Ausgabe
3. Fixed Window
Zählt Anfragen innerhalb fester Zeitfenster:
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);
Problem: Erlaubt bis zum 2-fachen des Limits an den Fenstergrenzen
4. Sliding Window Log
Unterhält ein Protokoll der Anfrage-Zeitstempel:
- Speichert einen Zeitstempel für jede Anfrage
- Entfernt Anfragen außerhalb des Fensters
- Genauer als Fixed Window
- Höherer Speicherverbrauch
5. Sliding Window Counter
Kombiniert Fixed Window mit Glättung:
// 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;
Praktische Implementierung
Mit Redis (Empfohlen für Produktion)
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();
});
Beliebte Bibliotheken
- express-rate-limit: Middleware für Express.js
- rate-limiter-flexible: Unterstützt mehrere Backends (Redis, Memcached, MySQL)
- Kong Rate Limiting: Plugin für API Gateway
- AWS API Gateway: Natives Rate Limiting
Erweiterte Strategien
Hierarchisches Rate Limiting
- Global: Gesamtlimit der API (z. B.: 1M req/min)
- Pro Benutzer: Individuelles Limit (z. B.: 1000 req/min)
- Pro Endpoint: Spezifische Limits (login: 5 req/min)
- Pro IP: Zusätzlicher Schutz vor Missbrauch
Dynamisches Rate Limiting
- Limits basierend auf der Systemlast anpassen
- Limits für Premium-Benutzer erhöhen
- Limits während Vorfällen reduzieren
Whitelisting und Blacklisting
- Vertrauenswürdige IPs/Benutzer ausnehmen
- Bekannte Angreifer dauerhaft blockieren
- Ein Reputationssystem implementieren
Bewährte Verfahren
- Informative Header zurückgeben (X-RateLimit-*)
- HTTP-Status 429 (Too Many Requests) verwenden
- Einen Retry-After-Header einschließen
- Limits in der API klar dokumentieren
- Exponentielles Backoff auf dem Client implementieren
- Rate-Limiting-Metriken überwachen
- Bei abnormalen Mustern alarmieren
- Limits vor der Produktion testen
Überwachungstools
- Grafana + Prometheus: Rate-Limiting-Metriken visualisieren
- Datadog: Überwachung und Alarme
- CloudWatch: Für APIs auf AWS
- New Relic: APM mit Rate-Limiting-Unterstützung
