Melindungi API dari lonjakan dan penyalahgunaan: rate limiting token bucket dengan tower-governor, throttling per-IP pada login dan endpoint publik, plus strategi timeout dan buffer untuk menahan DoS.

Fitur yang tidak terbatas adalah undangan penyalahgunaan: login bisa dibombardir tebakan password (brute-force), endpoint publik bisa dihantam bot, dan satu IP bisa menghabiskan seluruh resource. Episode ini memasang rem — rate limiting dan pertahanan DoS.
Mengapa rate limiting penting bahkan sebelum aplikasi populer? Karena penyalahgunaan tidak menunggu popularitas — crawler dan bot menyerang endpoint publik dalam hitungan menit setelah deploy. Rate limiting adalah pertahanan murah yang mencegah kerusakan mahal di kemudian hari.
tower-governor menerapkan token bucket: bucket berisi token; tiap request mengonsumsi satu token; bucket diisi ulang dengan kecepatan tetap. Request yang datang saat bucket kosong ditolak (429 Too Many Requests).
Dua parameter yang menentukan perilaku:
Misal 1 token/detik dengan burst 5: klien bisa kirim 5 request sekaligus, lalu harus menunggu 1 detik antar request. Ini melindungi dari banjir tanpa menghukum burst normal (misal refresh halaman).
cargo add tower_governor --features rtrbContoh konfigurasi per-IP dengan burst yang wajar:
use std::num::NonZeroU32;
use tower_governor::{
governor::Quota,
GovernorConfig,
GovernorConfigBuilder,
};
fn rate_limit_config() -> GovernorConfig {
GovernorConfigBuilder::default()
.per_second(10)
.burst_size(NonZeroU32::new(20).unwrap())
.key_extractor(tower_governor::key_extractors::SmartIpKeyExtractor)
.finish()
.expect("gagal build rate limit config")
}
fn app() -> Router {
let rate_limiter = tower_governor::GovernorLayer {
config: Arc::new(rate_limit_config()),
};
Router::new()
.route("/api/health", axum::routing::get(|| async { "ok" }))
.layer(rate_limiter)
}per_second(10) — 10 token per detik per IP.burst_size(20) — lonjakan 20 request tetap diterima.SmartIpKeyExtractor — mengelompokkan klien per IP (memperhatikan proxy via X-Forwarded-For; pastikan header ini tepercaya — lihat peringatan di bawah).Request yang melewati limit mendapat 429 Too Many Requests dari governor, plus header Retry-After.
Warning
SmartIpKeyExtractor memercayai X-Forwarded-For — jika aplikasi kalian langsung terekspos publik (tanpa proxy), attacker bisa memalsukan header untuk melewati limit. Pastikan header ini hanya tepercaya dari reverse proxy kalian sendiri (episode 20), atau gunakan key extractor berbasis IP koneksi langsung.
Endpoint login perlu aturan yang lebih ketat — tebakan password (brute-force) adalah serangan paling umum:
use std::num::NonZeroU32;
use tower_governor::{
governor::Quota,
key_extractors::SmartIpKeyExtractor,
GovernorConfigBuilder,
};
fn login_rate_limit() -> GovernorConfig {
GovernorConfigBuilder::default()
.per_second(1) // 1 percobaan per detik
.burst_size(NonZeroU32::new(5).unwrap()) // max 5 percobaan beruntun
.key_extractor(SmartIpKeyExtractor)
.finish()
.expect("gagal build login rate limit")
}
fn auth_routes() -> Router {
Router::new()
.route("/auth/login", axum::routing::post(login))
.layer(tower_governor::GovernorLayer {
config: Arc::new(login_rate_limit()),
})
}Angka yang masuk akal untuk login: 3-10 percobaan per menit per IP. Terlalu longgar tidak berguna, terlalu ketat mengganggu user. Setelah throttle per-IP, pertimbangkan throttle per-akun (berdasarkan username) sebagai lapisan kedua — mencegah attacker yang menyebar dari banyak IP menebak satu akun. Kombinasi ini adalah pertahanan brute-force standar.
Tip
Bonus keamanan login: gabungkan rate limit dengan delayed response seragam dan pesan error identik (dari episode 13). Serangan yang ditebak datanya dari timing dan pesan menjadi jauh lebih sulit.
Untuk endpoint yang terbuka ke publik (misal /posts), pilih limit yang mengizinkan normal use tetapi menahan bot:
fn public_api_limit() -> GovernorConfig {
GovernorConfigBuilder::default()
.per_second(5)
.burst_size(NonZeroU32::new(15).unwrap())
.key_extractor(SmartIpKeyExtractor)
.finish()
.unwrap()
}Tentukan angka dari traffic nyata: ukur puncak request per IP dalam satu menit di log (episode 17) lalu setel limit sedikit di atas itu. Memilih angka tanpa data = menebak.
Rate limiting menangani abuse per-IP. DoS yang lebih masif butuh lapisan tambahan:
TimeoutLayer membatasi durasi setiap request — pencegah serangan yang "menggantung" koneksi (slowloris):
use std::time::Duration;
use tower_http::timeout::TimeoutLayer;
.layer(TimeoutLayer::new(Duration::from_secs(30)))Request yang melebihi batas dibatalkan dan worker dibebaskan.
Beberapa endpoint mahal (query berat, kompresi besar) bisa disampling untuk batasan lebih ketat:
use std::time::Duration;
use tower_http::timeout::TimeoutLayer;
fn heavy_routes() -> Router {
Router::new()
.route("/reports/export", axum::routing::get(export_report))
.layer(TimeoutLayer::new(Duration::from_secs(120)))
// rate limit lebih rendah untuk endpoint mahal
.layer(tower_governor::GovernorLayer {
config: Arc::new(heavy_endpoint_limit()),
})
}
fn heavy_endpoint_limit() -> tower_governor::GovernorConfig {
tower_governor::GovernorConfigBuilder::default()
.per_second(1)
.burst_size(NonZeroU32::new(2).unwrap())
.key_extractor(SmartIpKeyExtractor)
.finish()
.unwrap()
}Endpoint ekspor besar yang memakan waktu 2 detik CPU per request tidak boleh melayani 1000 request/menit dari satu IP — throttle dengan limit yang proporsional terhadap biayanya.
Pada titik tertentu, pertahanan DoS berpindah dari aplikasi ke infrastruktur — reverse proxy membatasi koneksi konkuren, CDN menyerap banjir bandwidth. Episode 20 dan 24 membahas arsitektur ini; di episode ini yang penting: aplikasi punya timeout + rate limit sebagai pertahanan pertama.
Gabungkan semuanya pada satu aplikasi — inilah "pertahanan berlapis" untuk traffic masuk:
use std::sync::Arc;
use std::time::Duration;
use tower_http::timeout::TimeoutLayer;
fn protected_app() -> Router {
Router::new()
.merge(auth_routes()) // rate limit login (ketat)
.merge(public_api()) // rate limit API publik
.merge(heavy_routes()) // timeout + limit khusus
.layer(TimeoutLayer::new(Duration::from_secs(30)))
}Urutan eksekusi (dari luar ke dalam): timeout global → rate limit login/publik → handler. Setiap lapisan menangani jenis serangan yang berbeda:
| Lapisan | Menangani |
|---|---|
| Timeout | Request menggantung, slowloris |
| Rate limit | Brute-force, bot, abuse per-IP |
| Body limit (episode 18) | Request raksasa |
| Auth & RBAC (episode 13) | Akses ilegal |
Pada episode 19 ini aplikasi kalian punya rem dan perisai:
per_second + burst_size.SmartIpKeyExtractor per-IP, dengan peringatan header X-Forwarded-For.Di episode 20 selanjutnya kita amankan transport: HTTPS & TLS — rustls dan axum-server, deployment di belakang reverse proxy (nginx/caddy), serta sertifikat yang benar. Sampai jumpa di episode 20!