Node.js İle Rate Limiting Kullanarak Apı Koruma Sistemi Nasıl Yapılır?

Node.js İle Rate Limiting Kullanarak Apı Koruma Sistemi Nasıl Yapılır?
Node.js İle Rate Limiting Kullanarak Apı Koruma Sistemi Nasıl Yapılır?

Gereksinimler ve Ön Hazırlık

Bu uygulamayı gerçekleştirmek için sisteminizde Node.js'in güncel bir sürümünün (LTS 22.x veya üzeri önerilir) kurulu olması gerekmektedir. Projenizi başlatmak için bir terminal açın ve aşağıdaki adımları izleyin.

# Proje klasörünü oluşturun
mkdir api-koruma-sistemi
cd api-koruma-sistemi

# Projeyi başlatın
npm init -y

# Gerekli paketleri kurun
npm install express express-rate-limit redis rate-limit-redis

Bu aşamada express web framework'ünü, express-rate-limit temel sınırlama kütüphanesini ve dağıtık sistemler için redis ile rate-limit-redis paketlerini kurduk. Redis kullanımı, birden fazla sunucu çalıştırdığınız (load balancer arkasında) durumlarda sınırlamaların merkezi bir şekilde tutulmasını sağlar.

Temel Rate Limiting Yapılandırması

İlk adım olarak, Express uygulamanıza temel bir sınırlayıcı ekleyelim. Bu, her IP adresinin belirli bir süre içinde kaç istek yapabileceğini belirler.

const express = require('express');
const { rateLimit } = require('express-rate-limit');

const app = express();

const limiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 dakika
    limit: 100, // Her IP için 100 istek
    standardHeaders: 'draft-8', // Rate limit bilgilerini header'larda gösterir
    legacyHeaders: false, // X-RateLimit-* header'larını devre dışı bırakır
    message: 'Çok fazla istek gönderdiniz, lütfen 15 dakika sonra tekrar deneyin.'
});

app.use(limiter);

app.get('/', (req, res) => res.send('API Aktif.'));
app.listen(3000);

Burada windowMs ile zaman penceresini, limit ile bu pencere içerisindeki maksimum istek sayısını tanımladık. standardHeaders kullanımı, istemciye ne kadar hakkı kaldığını bildirmek için modern bir yaklaşımdır.

Redis ile Dağıtık Rate Limiting

Tek bir sunucuda bellek (RAM) kullanımı yeterli olabilir ancak üretim ortamında (production) genellikle birden fazla sunucu örneği çalıştırırsınız. Bu durumda limitleri merkezi bir Redis sunucusunda tutmak zorunludur.

const { RateLimitRedis } = require('rate-limit-redis');
const { createClient } = require('redis');

const client = createClient({ url: 'redis://localhost:6379' });
client.connect();

const redisLimiter = rateLimit({
    windowMs: 60 * 1000,
    limit: 50,
    store: new RateLimitRedis({
        sendCommand: (...args) => client.sendCommand(args),
    }),
});

app.use('/api/', redisLimiter);

Redis kullanımı, sunucunuz yeniden başlatılsa bile limitlerin sıfırlanmamasını sağlar ve yatay ölçeklenen sistemlerde tutarlı bir koruma sağlar.

Farklı Rotalar İçin Dinamik Limitler

Her API endpoint'i aynı güvenlik seviyesine ihtiyaç duymaz. Örneğin, `/login` rotası için daha katı, `/products` için daha esnek limitler belirlemek en iyi uygulamadır.

const loginLimiter = rateLimit({
    windowMs: 60 * 60 * 1000, // 1 saat
    limit: 5, // 1 saatte en fazla 5 giriş denemesi
    message: 'Çok fazla başarısız giriş denemesi.'
});

app.post('/api/login', loginLimiter, (req, res) => {
    // Giriş mantığı burada
});

Bu yöntemle, Brute Force (kaba kuvvet) saldırılarına karşı en kritik noktalarınızı (giriş, şifre sıfırlama) özel olarak korumaya almış olursunuz.

Rate Limiting Yöntemlerinin Karşılaştırılması

Yöntem Avantaj Dezavantaj
In-Memory Çok hızlı, kurulum gerektirmez Sunucu kapanınca veriler silinir
Redis (Dağıtık) Sunucular arası senkronize, kalıcı Ekstra altyapı maliyeti
Database (SQL) Detaylı loglama Veritabanına yük bindirir (yavaş)

Kritik Uyarı: Rate limiting tek başına bir güvenlik çözümü değildir. Her zaman Helmet.js gibi güvenlik başlıkları ekleyen kütüphanelerle birlikte kullanın ve API'nizde mutlaka JWT veya OAuth2 gibi kimlik doğrulama mekanizmaları bulundurun.

Hata Ayıklama ve İzleme

Uygulamanızın doğru çalışıp çalışmadığını anlamak için onLimitReached veya handler özelliklerini kullanarak loglama yapabilirsiniz. Bu, sisteminize kimin saldırdığını veya hangi istemcinin limitlere takıldığını görmenizi sağlar.

const limiter = rateLimit({
    windowMs: 15 * 60 * 1000,
    limit: 100,
    handler: (req, res, next, options) => {
        console.warn(`Limit aşıldı: ${req.ip}`);
        res.status(options.statusCode).send(options.message);
    }
});

Bu kod bloğu, limit aşıldığında sunucu konsoluna uyarı düşürür. Üretim ortamında bu logları bir merkezi loglama sistemine (ELK stack veya Winston) aktarmanız önerilir.

Sıkça Sorulan Sorular

Rate limiting performansı etkiler mi?

Redis kullanıldığında ağ gecikmesi minimal düzeydedir ve modern sistemlerde performans kaybı ihmal edilebilir seviyededir. In-memory kullanımı ise neredeyse sıfır gecikme sağlar.

Neden IP adresi yerine kullanıcı ID'si kullanmıyorum?

IP adresi, giriş yapmamış kullanıcıları korumak için en iyi yöntemdir. Giriş yapmış kullanıcılar için keyGenerator fonksiyonunu özelleştirerek req.user.id üzerinden limit uygulayabilirsiniz.

Proxy arkasındaki sunucularda ne yapmalıyım?

Eğer Nginx veya Cloudflare gibi bir proxy kullanıyorsanız, Express'te app.set('trust proxy', 1) ayarını mutlaka yapmalısınız. Aksi halde tüm istekler proxy'nin IP'sinden geliyormuş gibi görünür.

Limit aşıldığında ne tür bir HTTP kodu dönmeliyim?

HTTP 429 (Too Many Requests) kodu, bu durum için standarttır ve kütüphane bunu otomatik olarak yönetir.

Redis sunucum çökerse ne olur?

rate-limit-redis kütüphanesi genellikle "fail-open" (başarısızlık durumunda izin ver) mantığıyla çalışır, ancak kritik sistemlerde Redis'i yüksek erişilebilirlikli (cluster) modda çalıştırmalısınız.

Yasal Sorumluluk Reddi: Burada paylaşılan kodlar eğitim amaçlıdır. Yazılım güvenliği, sürekli güncellenmesi gereken bir süreçtir. Uygulamanızın güvenliğini sağlamak için bağımlılıklarınızı düzenli olarak güncelleyin ve profesyonel güvenlik testleri yaptırın.

Rate Limiting Test Stratejileri ve Yük Testi

Rate limiting yapılandırmanızın beklendiği gibi çalışıp çalışmadığını doğrulamak için üretim ortamına almadan önce mutlaka test etmelisiniz. Sadece manuel istekler göndermek, eşzamanlı (concurrent) isteklerin yarattığı "race condition" durumlarını veya Redis bağlantı gecikmelerini tespit etmek için yeterli değildir.

Artillery veya k6 gibi araçlar, API'nize kısa sürede yoğun trafik göndererek limitlerin tetiklenme anını gözlemlemenize olanak tanır. Aşağıda, k6 kullanarak belirli bir rotaya 1 saniyede 50 istek gönderen basit bir test senaryosu bulunmaktadır:

import http from 'k6/http';
import { check } from 'k6';

export const options = {
  vus: 10,
  duration: '10s',
};

export default function () {
  const res = http.get('http://localhost:3000/api/data');
  check(res, {
    'status is 429': (r) => r.status === 429,
  });
}

Bu testi çalıştırdığınızda, express-rate-limit veya Redis tabanlı yapılandırmanızın 429 hata kodunu dönüp dönmediğini ve Redis'in bu yoğunlukta CPU kullanımını izleyerek sistemin darboğaz noktasını belirleyebilirsiniz.

İleri Seviye: "Burst" ve "Token Bucket" Algoritmaları

Standart rate limiting genellikle sabit bir pencere (fixed window) mantığıyla çalışır. Ancak, kullanıcılarınızın kısa süreli yoğunluklarına (burst) izin vermek, kullanıcı deneyimini iyileştirebilir. Token Bucket algoritması, kullanıcının belirli bir "token" havuzuna sahip olmasını sağlar; kullanıcı istek attıkça bu havuzdan token eksilir, boş zamanlarda ise tokenlar tekrar dolar.

Bu yaklaşımı uygulamak için rate-limiter-flexible kütüphanesi oldukça esnek bir yapı sunar. Aşağıda, saniyede 5 istek hakkı olan ancak 10 isteğe kadar "burst" (ani çıkış) yapabilen bir yapılandırma örneği yer almaktadır:

const { RateLimiterMemory } = require('rate-limiter-flexible');

const limiter = new RateLimiterMemory({
  points: 5, // Saniyede 5 istek
  duration: 1, // 1 saniyelik periyot
  burst: 10, // Maksimum 10 isteğe kadar birikime izin ver
});

app.use((req, res, next) => {
  limiter.consume(req.ip)
    .then(() => next())
    .catch(() => {
      res.status(429).send('Çok fazla istek gönderildi, lütfen bekleyin.');
    });
});

Neden Token Bucket Tercih Edilmeli?

  • Daha Yumuşak Geçişler: Sabit pencere yöntemindeki "pencere sıfırlanma anı"ndaki ani yüklenmeleri engeller.
  • Esneklik: Kullanıcıların kısa süreli işlemlerini (örneğin bir formun hızlıca doldurulup gönderilmesi) engellemeden, uzun vadeli bot aktivitelerini kısıtlar.
  • Düşük Hata Oranı: Kullanıcılar, limitin tam dolduğu milisaniyede değil, havuz tamamen boşaldığında hata alırlar.

Bu yöntem, özellikle yüksek trafikli mikroservis mimarilerinde, servisler arası iletişimde "backpressure" yönetimi sağlamak için de idealdir.

Sonuç

Node.js ile rate limiting uygulamak, API'nizin dayanıklılığını artıran en temel güvenlik adımlarından biridir. Bu rehberde, temel sınırlayıcılardan Redis tabanlı dağıtık sistemlere kadar kapsamlı bir yol izledik. Bir sonraki adım olarak, API'nize "Rate Limiting" ile birlikte "Request Validation" (Joi veya Zod kütüphaneleri ile) ekleyerek gelen verilerin yapısını da koruma altına almanızı öneririm. Güvenli kodlamalar!

Bu yazıya tepkinizi paylaşın:
Mert Özdemir

Teknoloji ve dijital dünyadaki karmaşık süreçleri adım adım rehberlerle sadeleştiriyorum. Kullanıcıların dijital araçlardan en yüksek verimi alması için anlaşılır ve öğretici içerikler yazıyorum.

Yorumlar (0)

Yorum Yaz