Rate Limiting ב-API: הגנה מפני ניצול לרעה
Rate limiting היא טכניקה חיונית להגנה על API מפני ניצול לרעה, צריכת משאבים מופרזת והתקפות מניעת שירות. מימוש נכון מבטיח זמינות למשתמשים לגיטימיים תוך חסימת התנהגות זדונית או מופרזת.
מדוע ליישם Rate Limiting?
- הגנה מפני DDoS: הפחתת התקפות מניעת שירות
- מניעת Scraping: הקשיה על חילוץ נתונים אוטומטי
- בקרת עלויות: מניעת צריכה מופרזת של משאבי מחשוב
- הבטחת איכות השירות: חלוקת משאבים באופן הוגן
- מניעת Brute Force: הגבלת ניסיונות אימות
אלגוריתמים של Rate Limiting
1. Token Bucket
אלגוריתם המתחזק "דלי" של tokens שמתמלאים מחדש לאורך זמן:
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);
יתרונות: מאפשר bursts מבוקרים, מחליק את התעבורה
חסרונות: מורכב יותר למימוש
2. Leaky Bucket
מעבד בקשות בקצב קבוע, כמו מים הדולפים מדלי:
- הבקשות נכנסות לדלי
- מעובדות בקצב קבוע
- העודף עולה על גדותיו (נדחה)
- מבטיח פלט אחיד
3. Fixed Window
סופר בקשות בחלונות זמן קבועים:
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);
בעיה: מאפשר עד פי 2 מהמגבלה בקצוות החלונות
4. Sliding Window Log
מתחזק יומן של חותמות זמן של בקשות:
- מאחסן חותמת זמן לכל בקשה
- מסיר בקשות מחוץ לחלון
- מדויק יותר מ-Fixed Window
- צריכת זיכרון גבוהה יותר
5. Sliding Window Counter
משלב Fixed Window עם החלקה:
// 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;
מימוש מעשי
עם Redis (מומלץ לסביבת ייצור)
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();
});
ספריות פופולריות
- express-rate-limit: Middleware עבור Express.js
- rate-limiter-flexible: תומך במספר backends (Redis, Memcached, MySQL)
- Kong Rate Limiting: Plugin עבור API Gateway
- AWS API Gateway: Rate limiting מובנה
אסטרטגיות מתקדמות
Rate Limiting היררכי
- גלובלי: מגבלה כוללת של ה-API (לדוגמה: 1M req/min)
- לכל משתמש: מגבלה אישית (לדוגמה: 1000 req/min)
- לכל Endpoint: מגבלות ספציפיות (login: 5 req/min)
- לכל IP: הגנה נוספת מפני ניצול לרעה
Rate Limiting דינמי
- התאמת מגבלות בהתבסס על עומס המערכת
- הגדלת מגבלות עבור משתמשי פרימיום
- הפחתת מגבלות במהלך תקריות
Whitelisting ו-Blacklisting
- פטור ל-IP/משתמשים מהימנים
- חסימה קבועה של תוקפים מוכרים
- מימוש מערכת מוניטין
שיטות עבודה מומלצות
- החזרת כותרות אינפורמטיביות (X-RateLimit-*)
- שימוש בסטטוס HTTP 429 (Too Many Requests)
- הכללת כותרת Retry-After
- תיעוד ברור של המגבלות ב-API
- מימוש backoff אקספוננציאלי בצד הלקוח
- ניטור מדדי rate limiting
- התראה על דפוסים חריגים
- בדיקת מגבלות לפני סביבת ייצור
כלי ניטור
- Grafana + Prometheus: הדמיית מדדי rate limiting
- Datadog: ניטור והתראות
- CloudWatch: עבור API ב-AWS
- New Relic: APM עם תמיכה ב-rate limiting
