Po niekoľkých dňoch trápenia som našiel niekoľkoriadkové riešenie problému, ako zobraziť ReCaptcha po niekoľkých hitoch na endpoint. Je to užitočné napríklad pri integrácii platobnej brány, kde tým zabezpečíte, že útočník nemôže zneužívať vašu aplikáciu na zisťovanie, ktoré čísla kariet sú reálne a ktoré nie. Vyžadovanie ReCaptcha po niekoľkých po sebe idúcich hitoch v krátkom čase výrazne znižuje tento útočný vektor. Pozrime sa na to, za predpokladu Laravel 8:
Trieda middleware jednoducho prepisuje
metódu handle
štandardného \Illuminate\Routing\Middleware\ThrottleRequests throttling
middleware:
<?php
namespace App\Middleware;
use App\Http\Middleware\MyReCaptchaMiddleware;
use Closure;
use Illuminate\Routing\Middleware\ThrottleRequests;
class MyReCaptchaThrottleRequests extends ThrottleRequests
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param int|string $maxAttempts
* @param float|int $decayMinutes
* @param string $prefix
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws \Illuminate\Http\Exceptions\ThrottleRequestsException
*/
public function handle($request, Closure $next, $maxAttempts = 60, $decayMinutes = 1, $prefix = '')
{
return $this->handleRequest(
$request,
$next,
[
(object) [
'key' => $prefix.$this->resolveRequestSignature($request),
'maxAttempts' => $this->resolveMaxAttempts($request, $maxAttempts),
'decayMinutes' => $decayMinutes,
'responseCallback' => fn() => app(MyReCaptchaMiddleware::class)->handle($request, $next),
],
]
);
}
Vyššie uvedené predpokladá, že už máte vlastný MyReCaptchaMiddleware v
prevádzke, do jeho detailov tu nebudeme zachádzať. Existuje mnoho návodov.
Bude zavolaný cez responseCallback, ktorý v rodičovskej funkcii je bežne
null. Väčšina mágie sa v podstate odohráva práve na tomto riadku.
Ďalej
pridajte
route middleware do app/Http/Kernel.php takto:
protected $routeMiddleware = [
// ...
'throttle_recaptcha' => MyReCaptchaThrottleRequests::class,
];
Posledným krokom je skutočne priradiť middleware throttle_recaptcha k
route:
Route::get('/endpoint', function () {
//
})->middleware('throttle_recaptcha');
Je možné pridať voliteľný parameter maxAttempts:
Route::get('/endpoint', function () {
//
})->middleware('throttle_recaptcha:10');
A dokonca aj decayMinutes ako druhý parameter:
Route::get('/endpoint', function () {
//
})->middleware('throttle_recaptcha:10,2');
Vyššie uvedené bude vyžadovať overenie ReCaptcha na našom endpointe po 10
hitoch a bude to trvať ďalšie 2 minúty. To je všetko. Poznámka — toto stále
rate-limituje samotný endpoint pre všetkých jeho konzumentov. V závislosti
od vašich potrieb môže byť potrebné viac doladenia na throttling endpointu
per-user! To sa dá ľahko urobiť prepisaním metódy
resolveRequestSignature()
method.
Pomenované limitery #
Existuje odsek, ktorý som zámerene odstránil z vrchnej rodičovskej funkcie
handle(). Pre záznam, je to
tento:
if (is_string($maxAttempts)
&& func_num_args() === 3
&& ! is_null($limiter = $this->limiter->limiter($maxAttempts))) {
return $this->handleRequestUsingNamedLimiter($request, $next, $maxAttempts, $limiter);
}
Odstránený kód slúži na užitočnú funkciu, voľne nazývanú
pomenované limitery,
kde na mieste parametra $maxAttempts je reťazec s názvom throttle rate
limitera:
Route::middleware(['throttle:my_rate_limiter'])->group(function () {
Route::post('/endpoint', function () {
//
});
});
Vyššie uvedené by použilo definíciu rate limitera takú:
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;
protected function configureRateLimiting()
{
RateLimiter::for('my_rate_limiter', function (Request $request) {
return Limit::perMinute(1000);
});
}
Keďže však radšej používame poskytnuté parametre middleware $maxAttempts
a $decayMinutes len na ich určený účel, môžeme ten blok bezpečne
vynechať. Tiež, z dôvodov, ktoré sú teraz snáď zrejmé, nie je možné
používať parametre s pomenovanými limitermi.
Ak chcete pomenované limitery používať aj s middleware
throttle_recaptcha, môžete ten blok ponechať. Neuškodí to. Kód som
považoval za mätúci, keďže parameter $maxAttempts nie je dobrý názov ani
pre skutočný maximálny počet pokusov, ani pre názov rate limitera. Mám
podozrenie, že funkcia pomenovaných limiterov bola pravdepodobne pridaná
dodatočne. Nechcel som ďalej mýliť kolegov počas code review, takže som ho
vynechal. Je to ale užitočná funkcia, tak to majte na pamäti.
Hlavičky rate limitera #
Existujú dve hlavičky, ktoré sa pridávajú k rate-limitovaným endpointom:
X-RateLimit-LimitX-RateLimit-Remainig
Tú druhú používam na určenie, kedy zobraziť ReCaptcha na front-ende.
Jednoducho, ak zostatok hitov zobrazený v hlavičke odpovede
X-RateLimit-Remainig sa rovná jednej, viem, že všetky nasledujúce
requesty budú vyžadovať ReCaptcha token, takže to je presne ten moment,
kedy dať používateľovi (alebo automatizovanému botovi) prejsť testom,
získať token a pripojiť ho k legitímnym nasledujúcim requestom.
Ako vedľajšia poznámka, X-RateLimit-Remainig teraz vždy ukáže dva hity,
kvôli problému diskutovanému
tu,
čo je nešťastné, ale dá sa obísť.
Príjemnú prácu!
Odkazy #
- https://laracasts.com/discuss/channels/laravel/call-a-middleware-from-another-middleware?reply=288672
- https://laracasts.com/discuss/channels/laravel/how-to-customize-throttle-error?reply=724946
- https://stackoverflow.com/questions/63873681/laravel-customize-response-headers-when-using-rate-limiting-middleware
- https://www.codecheef.org/article/how-to-implement-rate-limiting-in-laravel-8
- https://bannister.me/blog/custom-throttle-middleware
- https://laracasts.com/discuss/channels/laravel/fortify-rate-throttling-redirecting-opposed-to-error-in-session?reply=696285
- https://www.cloudways.com/blog/laravel-and-api-rate-limiting/
- https://dev.to/aliadhillon/new-simple-way-of-creating-custom-rate-limiters-in-laravel-8-65n
- https://stackoverflow.com/questions/66102519/laravel-ratelimiter-throttle-increasing-decay-minutes?rq=1
- https://stackoverflow.com/questions/70820870/laravel-rate-limiter-limits-access-wrongly-after-only-one-attempt
- https://laraveldaily.com/laravel-too-many-login-attempts-restrict-and-customize/
- https://www.tutorialsbuddy.com/adding-google-recaptcha-in-laravel