Vai al contenuto principale

Come funziona il facade RateLimiter

Il rate limiting di Laravel è formato dalla classe Illuminate\Cache\RateLimiting\Limit e dal facade RateLimiter. Internamente memorizza contatori nel driver di cache (di default file o Redis) e tiene traccia del numero di richieste. Sulla richiesta ricevuta dal middleware throttle, viene eseguita la closure definita con RateLimiter::for(): se il limite è raggiunto, viene restituito 429 Too Many Requests.

Definire un limiter personalizzato in AppServiceProvider

Definisci le impostazioni di rate limit nel metodo boot() di App\Providers\AppServiceProvider.
<?php

namespace App\Providers;

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        RateLimiter::for('api', function (Request $request) {
            return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
        });
    }
}
Il primo argomento di RateLimiter::for() è il nome del limiter, usato quando lo referenzi dal middleware throttle. La closure passata come secondo argomento deve restituire un’istanza Illuminate\Cache\RateLimiting\Limit.

Rate limit per utente, IP e piano

Limiti diversi tra utenti autenticati e ospiti

RateLimiter::for('uploads', function (Request $request) {
    return $request->user()
        ? Limit::perHour(100)->by($request->user()->id)
        : Limit::perHour(10)->by($request->ip());
});

Limiti in base al piano dell’utente

RateLimiter::for('api', function (Request $request) {
    $user = $request->user();

    if (! $user) {
        return Limit::perMinute(30)->by($request->ip());
    }

    return match ($user->plan) {
        'enterprise' => Limit::none(),
        'pro'        => Limit::perMinute(500)->by($user->id),
        default      => Limit::perMinute(60)->by($user->id),
    };
});

Limite globale per IP

Throttling per indirizzo IP, indipendentemente da endpoint specifici.
RateLimiter::for('global', function (Request $request) {
    return Limit::perMinute(1000)->by($request->ip());
});

Combinare più limiti

Restituendo un array, tutti i limiti vengono valutati. Basta che uno sia raggiunto per restituire 429.
RateLimiter::for('login', function (Request $request) {
    return [
        Limit::perMinute(10)->by($request->ip()),
        Limit::perMinute(5)->by($request->input('email')),
    ];
});
Quando definisci più limiti con lo stesso valore di by, aggiungi un prefisso per evitare conflitti tra chiavi.
RateLimiter::for('uploads', function (Request $request) {
    return [
        Limit::perMinute(10)->by('minute:' . $request->user()->id),
        Limit::perDay(1000)->by('day:' . $request->user()->id),
    ];
});

Middleware throttle e specifica del nome del limiter

Al middleware throttle passi il nome del limiter definito.
use Illuminate\Support\Facades\Route;

Route::middleware(['throttle:api'])->group(function () {
    Route::get('/user', function () { /* ... */ });
    Route::post('/posts', function () { /* ... */ });
});

Registrazione in bootstrap/app.php

Da Laravel 11, i middleware si gestiscono in bootstrap/app.php.
use Illuminate\Foundation\Application;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__ . '/../routes/web.php',
        api: __DIR__ . '/../routes/api.php',
        apiPrefix: 'api',
    )
    ->withMiddleware(function (\Illuminate\Foundation\Configuration\Middleware $middleware): void {
        $middleware->throttleApi('api');
    })
    ->create();

Esempio di applicazione alle rotte API

1

Definire i limiter

Definisci più limiter in AppServiceProvider.
public function boot(): void
{
    // accesso API generico
    RateLimiter::for('api', function (Request $request) {
        return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
    });

    // upload di file
    RateLimiter::for('uploads', function (Request $request) {
        return $request->user()?->isPro()
            ? Limit::perHour(500)->by($request->user()->id)
            : Limit::perHour(50)->by($request->user()?->id ?: $request->ip());
    });

    // tentativi di login
    RateLimiter::for('login', function (Request $request) {
        return [
            Limit::perMinute(10)->by($request->ip()),
            Limit::perMinute(5)->by($request->input('email')),
        ];
    });
}
2

Applicare il middleware alle rotte

// routes/api.php
use Illuminate\Support\Facades\Route;

Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
    Route::get('/user', [\App\Http\Controllers\UserController::class, 'show']);
    Route::get('/posts', [\App\Http\Controllers\PostController::class, 'index']);
});

Route::middleware(['auth:sanctum', 'throttle:uploads'])->group(function () {
    Route::post('/uploads', [\App\Http\Controllers\UploadController::class, 'store']);
});

Route::middleware(['throttle:login'])->group(function () {
    Route::post('/login', [\App\Http\Controllers\AuthController::class, 'login']);
});

Come funzionano gli header di risposta (X-RateLimit-*)

Il middleware throttle aggiunge automaticamente alle response gli header con le informazioni di limite.
HeaderDescrizione
X-RateLimit-LimitNumero di richieste consentite
X-RateLimit-RemainingNumero di richieste rimanenti
Retry-AfterSecondi prima della prossima richiesta possibile (solo su 429)
X-RateLimit-ResetTimestamp UNIX di reset del limite
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
Retry-After: 45
X-RateLimit-Reset: 1717000000
Content-Type: application/json

{
    "message": "Too Many Requests."
}

Restituire una response personalizzata

RateLimiter::for('api', function (Request $request) {
    return Limit::perMinute(60)
        ->by($request->user()?->id ?: $request->ip())
        ->response(function (Request $request, array $headers) {
            return response()->json([
                'message' => 'Hai superato il limite di richieste. Riprova più tardi.',
                'retry_after' => $headers['Retry-After'],
            ], 429, $headers);
        });
});

Controllo manuale con RateLimiter::attempt()

Se vuoi verificare il rate limit in un punto specifico del codice, senza usare il middleware throttle, usa RateLimiter::attempt().
use Illuminate\Support\Facades\RateLimiter;

class SmsController extends Controller
{
    public function send(Request $request): \Illuminate\Http\JsonResponse
    {
        $key = 'sms:' . $request->user()->id;

        $executed = RateLimiter::attempt(
            key: $key,
            maxAttempts: 5,
            callback: function () use ($request) {
                app(SmsService::class)->send(
                    $request->user()->phone,
                    $request->input('message')
                );
            },
            decaySeconds: 3600,  // 1 ora
        );

        if (! $executed) {
            $seconds = RateLimiter::availableIn($key);

            return response()->json([
                'message' => "Hai superato il limite di invio SMS. Riprova tra {$seconds} secondi.",
            ], 429);
        }

        return response()->json(['message' => 'SMS inviato.']);
    }
}

Verifica dei tentativi e reset

// numero di tentativi correnti
$hits = RateLimiter::attempts($key);

// secondi al prossimo reset
$seconds = RateLimiter::availableIn($key);

// verifica se il limite è stato raggiunto
$tooMany = RateLimiter::tooManyAttempts($key, $maxAttempts = 5);

// reset manuale del contatore (dopo logout, ecc.)
RateLimiter::clear($key);

Esempio di throttling del login

public function login(Request $request): mixed
{
    $key = 'login:' . $request->input('email');

    if (RateLimiter::tooManyAttempts($key, 5)) {
        $seconds = RateLimiter::availableIn($key);

        throw ValidationException::withMessages([
            'email' => "Troppi tentativi di login. Riprova tra {$seconds} secondi.",
        ]);
    }

    if (! Auth::attempt($request->only('email', 'password'))) {
        RateLimiter::hit($key, 300);  // conta per 5 minuti

        throw ValidationException::withMessages([
            'email' => 'Email o password non corretti.',
        ]);
    }

    RateLimiter::clear($key);

    return redirect()->intended('/dashboard');
}

Rate limit basato sulla risposta

Se vuoi contare solo determinate response, usa after(). Esempio in cui conti solo le 404 per prevenire attacchi di enumerazione di risorse:
use Symfony\Component\HttpFoundation\Response;

RateLimiter::for('resource-lookup', function (Request $request) {
    return Limit::perMinute(10)
        ->by($request->user()?->id ?: $request->ip())
        ->after(function (Response $response) {
            return $response->getStatusCode() === 404;
        });
});

Rate limiting con Redis

Basta cambiare il driver di cache predefinito in Redis e anche il middleware throttle userà Redis automaticamente.

Configurazione del driver Redis

// config/cache.php
'default' => env('CACHE_DRIVER', 'redis'),
# .env
CACHE_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379

Usare throttleWithRedis

Per usare il throttling middleware ottimizzato per Redis, chiama throttleWithRedis() in bootstrap/app.php.
use Illuminate\Foundation\Application;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__ . '/../routes/web.php',
        api: __DIR__ . '/../routes/api.php',
        apiPrefix: 'api',
    )
    ->withMiddleware(function (\Illuminate\Foundation\Configuration\Middleware $middleware): void {
        $middleware->throttleWithRedis();
    })
    ->create();
Così il middleware throttle viene mappato sulla classe ThrottleRequestsWithRedis e il conteggio avviene con precisione grazie alle operazioni atomiche di Redis.
Se usi throttleWithRedis(), assicurati che Redis sia sempre disponibile. In caso di errore di connessione a Redis, tutte le richieste potrebbero essere rifiutate.

Vantaggi di usare Redis

  • Compatibile con la scalabilità orizzontale — puoi condividere il contatore tra più istanze del server
  • Alta precisione — le operazioni atomiche prevengono race condition
  • Gestione del TTL — l’expiration nativo di Redis rimuove automaticamente i contatori

Pagine correlate

Cache

Rivedi configurazione e uso dei driver di cache di Laravel, Redis incluso.
Ultima modifica il 13 luglio 2026