RateLimit – Ochrana proti floodu
Princip: Omezit počet requestů ze stejné IP adresy v daném časovém okně.
Používá se k ochraně login endpointů, registrace, API antes brute-force útoků. RateLimiter si pamatuje timestampy posledních requestů z každé IP adresy v JSON souborech a při překročení limitu request zamítne.
Proč se používá
- Brute-force ochrana – Útočník nemůže zkoušet 1000 hesel za sekundu
- DDoS mitigation – Sníží dopadu nevyškoleného DDoS scripperu
- API rate limiting – Zamezí zneužití API endpointů
- Login flood – Ochrana přihlašovacích formulářů
Jak to funguje (vnitřně)
Sliding Window Approach
Každá IP adresa má svůj JSON soubor v var/rate-limits/:
var/rate-limits/
├── a3f9c2d1e8b47f02.json ← IP 192.168.1.100 (SHA-256 hash)
├── f7d2b1c9e3a4f6d8.json ← IP 10.0.0.50
└── ...
Obsah souboru:
{
"requests": [1742468100, 1742468105, 1742468110, 1742468115]
}
Pole requests obsahuje timestampy poslední aktivit (Unix timestamps). Přesné pořadí se neuchovává – jen hodnoty.
Jak se filtrují staré requesty
Při každém check():
- Načte pole timestampů
- Vyfiltruje ty, které jsou mimo časové okno (sliding window)
- Pokud zbývá málo requestů → přidá aktuální čas
- Pokud je víc → zamítne (vrátí false)
Příklad s check($ip, 5, 60) (5 requestů / 60 sekund):
čas 100: [100] → accept
čas 105: [100, 105] → accept
čas 110: [100, 105, 110] → accept
čas 115: [100, 105, 110, 115] → accept
čas 120: [100, 105, 110, 115, 120] → accept
čas 125: [100, 105, 110, 115, 120] → REJECT (6 requestů)
čas 162: [105, 110, 115, 120] → accept (100 expiroval, < 60s od teď)
Race Conditions a Locking
RateLimiter používá file locking (flock) na úrovni OS:
$fp = fopen($file, 'c+');
flock($fp, LOCK_EX); // Exclusive lock – ostatní čekají
// ... read/write/modify ...
flock($fp, LOCK_UN); // Release
fclose($fp);
To zabraňuje race conditions v high-concurrency prostředí (když více requestů z různých IP procesů souběžně zásahuje do var/rate-limits/).
Fail-open behavior: Pokud se nepodaří otevřít soubor nebo získat zámek, request je povolen (raději flood než false-positive block).
Výkon a Dopad na Přihlášení
Správný Výkon (Optimální Nastavení)
Jeden request: ~1-2ms
Pokud voláš RateLimiter jen na login endpointu, je to zanedbatelný overhead.
Zpomalení Přihlášení?
Ano, ale jen pokud voláš check() globálně na každý request (včetně statických souborů, AJAX, API):
// ❌ ŠPATNě – voláš na všechno
if (!RateLimiter::check($ip)) {
http_response_code(429);
exit;
}
Problém:
- Login formulář = 10-15 requestů (HTML + CSS + JS + AJAX)
- Všechny čekají na zámek stejného souboru
- Fronta na zámku = viditelné zpomalení
Řešení
Volej RateLimiter jen tam, kde to má smysl:
// ✅ DOBŘE – jen na login
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $path === '/login') {
if (!RateLimiter::check($ip, 10, 300)) {
http_response_code(429);
exit;
}
}
Nebo filtruj statické soubory:
// ✅ DOBŘE – vynech statické assety
if (!in_array(pathinfo($path, PATHINFO_EXTENSION), ['js', 'css', 'png', 'jpg'])) {
RateLimiter::check($ip);
}
Případ Použití
| Scénář | Limit | Okno |
|---|---|---|
| Login form | 10 pokusů | 5 minut |
| API endpoint | 100 requestů | 1 minuta |
| Public form (kontakt) | 5 | 1 hodina |
| Password reset | 3 | 1 hodina |
Ochrana Soukromí
- IP adresy jsou hashované (SHA-256, 16 znaků)
- Žádné plaintext IP se neukládá
- Soubory mají TTL 1 hodinu (automaticky se mažou staré)
- GDPR compliant
Přenositelnost & Nezávislost
RateLimiter je zcela přenositelný – žádné závislosti:
✅ Žádné externí balíčky (Composer)
✅ Žádné systémové příkazy (shell)
✅ Žádné databáze (MySQL, PostgreSQL)
✅ Žádné cache servery (Redis, Memcached)
✅ Jen PHP built-in funkce:
fopen(),flock(),fwrite()– File I/Ojson_encode()– JSON (PHP 5.2+)hash('sha256')– SHA-256 hashingmkdir()– Directory creationtime()– Unix timestamps
Filesystem: Jedinou závislostí je filesystem. Každý server/hosting má filesystém.
PHP verze: 7.1+ (standardní)
Kopírování na Jiný Projekt
# Zkopíruj jen core/RateLimit/:
cp -r core/RateLimit/ /your-project/
# To je vše!
Pak jen zavolej v boostrappingu:
RateLimiter::setPath('/path/to/rate-limits');
Nastavení Storage Path
NUTNÉ: Musíš zavolat setPath() během bootstrap aplikace!
// app/bootstrap.php
RateLimiter::setPath(DIR . '/var/rate-limits');
// Nebo vlastní cesta:
RateLimiter::setPath('/tmp/rate-limits');
Bez setPath() vyhodí RuntimeException (fail-fast design).
Výchozí Cesta
Neexistuje – musíš ji nastavit sám. Tím se zajistí explicitní inicializace a žádné "tiché selhání".
Lazy Cleanup
RateLimiter automaticky maže starší soubory (~1% šance na každý allowed request):
if ($allowed && random_int(1, 100) === 1) {
self::cleanupOldFiles(); // Smaž soubory starší 1h
}
Zabraňuje neomezené akumulaci souborů na disku.
Další Čtení
- API Reference – kompletní funkce
- Příklady Použití – praktické scénáře