Merhaba arkadaşlar, bugün başımı çok ağrıtan bir konudan bahsedeceğim. Bir API projesinde, klasik IP bazlı rate limiting çözümleri artık yeterli gelmiyordu. Kötü niyetli bir kullanıcı, tüm endpoint'lerimize aynı limit içinde saldırı yapabiliyor veya premium bir kullanıcı ile ücretsiz kullanıcıyı aynı kefeye koymak adil olmuyordu. İşte bu sorunu nasıl katman katman çözüme kavuşturduğumu anlatacağım.
Klasik Çözüm Neden Yetmedi?
Geleneksel olarak, bir middleware ile sadece IP adresini kontrol ediyorduk. Kodu aşağıda paylaşıyorum:
Bu kod, her şeyi güzelce sınırlıyordu ama /api/profile endpoint'ine yapılan zararsız isteklerle, /api/checkout gibi kritik endpoint'lere yapılan istekler aynı limitten tüketiyordu. Ayrıca, giriş yapmış bir kullanıcı ile anonim bir ziyaretçi de aynı muameleyi görüyordu. Bu adaletsizlik beni rahatsız etti!
Çok Katmanlı (Multi-Layer) Stratejim
Çözüm olarak, rate limiting mantığını üç katmana ayırmaya karar verdim:
1. Global IP Limiti: Sunucuyu korumak için en temel kalkan.
2. Kullanıcı Bazlı Limit: Kimlik doğrulaması yapılmış kullanıcılar için, planlarına (ücretsiz, premium, admin) göre esnek limitler.
3. Endpoint Bazlı Limit: Her bir API yolunun kritik seviyesine göre özel limitler.
Bu yaklaşım için express-rate-limit ve redis kullanarak merkezi bir sayaç yönetimi kurdum.
Redis ile Esnek Limit Yönetimi
Öncelikle, kullanıcı tiplerine ve endpoint'lere göre limit kurallarımızı bir konfigürasyon objesinde tanımladım.
Akıllı Middleware'im
Sonrasında, gelen her isteği analiz edip, ona uygun limit kurallarını dinamik olarak uygulayan bir middleware yazdım. Burada sihir, generateKey fonksiyonunda.
Sonuç ve Kazanımlar
Bu yapıyı kurduktan sonra inanılmaz rahatladım. Artık:
Kritik /api/checkout endpoint'i, spam ödeme isteklerine karşı çok daha korunaklı.
Premium bir kullanıcı, ücretsiz kullanıcıya göre 5 kat daha fazla API isteği atabiliyor.
/api/auth/login üzerinde bruteforce saldırı riski ciddi oranda azaldı.
Tüm bu kurallar, merkezi Redis sunucusunda tutulduğu için birden fazla sunucu (cluster) kullansak bile tutarlı çalışıyor.
Siz backend API'larınızda rate limiting'i nasıl yönetiyorsunuz? Özellikle mikroservis mimarisinde bu kuralları nasıl merkezileştiriyorsunuz? Benim kullandığım yöntem hakkında düşünceleriniz veya eklemek istedikleriniz var mı? Yorumlarda buluşalım!
Geleneksel olarak, bir middleware ile sadece IP adresini kontrol ediyorduk. Kodu aşağıda paylaşıyorum:
JavaScript:
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 60 1000, // 15 dakika
max: 100, // IP başına 100 istek
message: 'Çok fazla istek gönderdiniz, lütfen 15 dakika sonra tekrar deneyin.'
});
app.use('/api', limiter);
Bu kod, her şeyi güzelce sınırlıyordu ama /api/profile endpoint'ine yapılan zararsız isteklerle, /api/checkout gibi kritik endpoint'lere yapılan istekler aynı limitten tüketiyordu. Ayrıca, giriş yapmış bir kullanıcı ile anonim bir ziyaretçi de aynı muameleyi görüyordu. Bu adaletsizlik beni rahatsız etti!
Çözüm olarak, rate limiting mantığını üç katmana ayırmaya karar verdim:
1. Global IP Limiti: Sunucuyu korumak için en temel kalkan.
2. Kullanıcı Bazlı Limit: Kimlik doğrulaması yapılmış kullanıcılar için, planlarına (ücretsiz, premium, admin) göre esnek limitler.
3. Endpoint Bazlı Limit: Her bir API yolunun kritik seviyesine göre özel limitler.
Bu yaklaşım için express-rate-limit ve redis kullanarak merkezi bir sayaç yönetimi kurdum.
Öncelikle, kullanıcı tiplerine ve endpoint'lere göre limit kurallarımızı bir konfigürasyon objesinde tanımladım.
JavaScript:
const rateLimitRules = {
// Kullanıcı Tiplerine Göre Global Dakikalık Limitler
userTier: {
anonymous: { windowMs: 60000, max: 60 }, // Dakikada 60 istek
free: { windowMs: 60000, max: 120 },
premium: { windowMs: 60000, max: 300 },
admin: { windowMs: 60000, max: 1000 }
},
// Endpoint'lere Özel Ek Limitler (Kullanıcı limitine EK olarak uygulanır)
endpoints: {
'/api/auth/login': { windowMs: 300000, max: 5 }, // 5 dakikada 5 başarısız giriş
'/api/checkout': { windowMs: 3600000, max: 10 }, // Saatte 10 satın alma
'/api/search': { windowMs: 10000, max: 30 } // 10 saniyede 30 arama
}
};
Sonrasında, gelen her isteği analiz edip, ona uygun limit kurallarını dinamik olarak uygulayan bir middleware yazdım. Burada sihir, generateKey fonksiyonunda.
JavaScript:
const Redis = require('ioredis');
const redisClient = new Redis(process.env.REDIS_URL);
const smartRateLimiter = (req, res, next) => {
// 1. Anahtar (Key) Oluştur
const ip = req.ip;
const userId = req.user?.id || 'anonymous';
const userTier = req.user?.tier || 'anonymous';
const path = req.path;
// Örnek Key: rateLimit:ip:192.168.1.1|user:123|path:/api/search
const baseKey = `rateLimit:ip:${ip}|user:${userId}|path:${path}`;
// 2. Uygulanacak Limit Kurallarını Belirle
const tierRule = rateLimitRules.userTier[userTier];
const endpointRule = rateLimitRules.endpoints[path];
const limitsToApply = [];
if (tierRule) limitsToApply.push({ ...tierRule, keySuffix: 'tier' });
if (endpointRule) limitsToApply.push({ ...endpointRule, keySuffix: 'endpoint' });
// 3. Tüm Limitleri Asenkron Kontrol Et
const checkPromises = limitsToApply.map(async (rule) => {
const fullKey = `${baseKey}|type:${rule.keySuffix}`;
const current = await redisClient.incr(fullKey);
if (current === 1) {
// İlk istekte, bu anahtar için TTL (süre) belirle
await redisClient.expire(fullKey, rule.windowMs / 1000);
}
if (current > rule.max) {
throw new Error(`LIMIT_EXCEEDED_${rule.keySuffix.toUpperCase()}`);
}
return true;
});
Promise.all(checkPromises)
.then(() => next()) // Tüm limitler aşılmadı, isteği geçir
.catch((err) => {
if (err.message.includes('LIMIT_EXCEEDED')) {
const limitType = err.message.split('_')[2];
res.status(429).json({
error: 'İstek Limiti Aşıldı',
message: `${limitType} limitinizi aştınız. Lütfen daha sonra tekrar deneyin.`,
retryAfter: limitsToApply.find(r => r.keySuffix === limitType.toLowerCase())?.windowMs / 1000
});
} else {
next(err);
}
});
};
// Middleware'i uygula
app.use('/api', smartRateLimiter);
Bu yapıyı kurduktan sonra inanılmaz rahatladım. Artık:
Kritik /api/checkout endpoint'i, spam ödeme isteklerine karşı çok daha korunaklı.
Premium bir kullanıcı, ücretsiz kullanıcıya göre 5 kat daha fazla API isteği atabiliyor.
/api/auth/login üzerinde bruteforce saldırı riski ciddi oranda azaldı.
Tüm bu kurallar, merkezi Redis sunucusunda tutulduğu için birden fazla sunucu (cluster) kullansak bile tutarlı çalışıyor.
Siz backend API'larınızda rate limiting'i nasıl yönetiyorsunuz? Özellikle mikroservis mimarisinde bu kuralları nasıl merkezileştiriyorsunuz? Benim kullandığım yöntem hakkında düşünceleriniz veya eklemek istedikleriniz var mı? Yorumlarda buluşalım!