Rate Limiting em APIs: Proteção Contra Abuso
Rate limiting é uma técnica essencial para proteger APIs contra abuso, consumo excessivo de recursos e ataques de negação de serviço. Uma implementação adequada garante disponibilidade para usuários legítimos enquanto bloqueia comportamentos maliciosos ou excessivos.
Por Que Implementar Rate Limiting?
- Proteção contra DDoS: Mitigar ataques de negação de serviço
- Prevenir Scraping: Dificultar extração automatizada de dados
- Controlar Custos: Evitar consumo excessivo de recursos computacionais
- Garantir Qualidade de Serviço: Distribuir recursos equitativamente
- Prevenir Brute Force: Limitar tentativas de autenticação
Algoritmos de Rate Limiting
1. Token Bucket
Algoritmo que mantém um "balde" de tokens que se reenchem ao longo do tempo:
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);
Vantagens: Permite bursts controlados, suaviza tráfego
Desvantagens: Mais complexo de implementar
2. Leaky Bucket
Processa requisições em taxa constante, como água vazando de um balde:
- Requisições entram no balde
- Processadas em taxa fixa
- Excesso transborda (rejeitado)
- Garante saída uniforme
3. Fixed Window
Conta requisições em janelas de tempo fixas:
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);
Problema: Permite até 2x o limite no edge de janelas
4. Sliding Window Log
Mantém log de timestamps de requisições:
- Armazena timestamp de cada requisição
- Remove requisições fora da janela
- Mais preciso que Fixed Window
- Maior consumo de memória
5. Sliding Window Counter
Combina Fixed Window com suavização:
// 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;
Implementação Prática
Com Redis (Produção Recomendada)
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();
});
Bibliotecas Populares
- express-rate-limit: Middleware para Express.js
- rate-limiter-flexible: Suporta múltiplos backends (Redis, Memcached, MySQL)
- Kong Rate Limiting: Plugin para API Gateway
- AWS API Gateway: Rate limiting nativo
Estratégias Avançadas
Rate Limiting Hierárquico
- Global: Limite total da API (ex: 1M req/min)
- Por Usuário: Limite individual (ex: 1000 req/min)
- Por Endpoint: Limites específicos (login: 5 req/min)
- Por IP: Proteção adicional contra abusos
Rate Limiting Dinâmico
- Ajustar limites baseado em carga do sistema
- Aumentar limites para usuários premium
- Reduzir limites durante incidentes
Whitelisting e Blacklisting
- Isentar IPs/usuários confiáveis
- Bloquear permanentemente atacantes conhecidos
- Implementar sistema de reputação
Melhores Práticas
- Retornar cabeçalhos informativos (X-RateLimit-*)
- Usar status HTTP 429 (Too Many Requests)
- Incluir Retry-After header
- Documentar limites claramente na API
- Implementar backoff exponencial no cliente
- Monitorar métricas de rate limiting
- Alertar sobre padrões anormais
- Testar limites antes de produção
Ferramentas de Monitoramento
- Grafana + Prometheus: Visualizar métricas de rate limiting
- Datadog: Monitoramento e alertas
- CloudWatch: Para APIs na AWS
- New Relic: APM com suporte a rate limiting
