Rate limiting e throttling no Laravel
Rate limiting e throttling são técnicas essenciais para controlar o tráfego de requisições em aplicações web. Embora frequentemente usados como sinônimos, eles têm diferenças sutis: rate limiting define um limite absoluto de requisições em um período (ex: 100 requisições/minuto), enquanto throttling é um mecanismo mais dinâmico que pode reduzir gradualmente a velocidade de processamento conforme o uso se aproxima do limite.
No Laravel, implementar esses controles é fundamental para proteger sua aplicação contra abusos como ataques de força bruta, scraping excessivo, DDoS e sobrecarga de recursos. O framework oferece duas ferramentas nativas poderosas: o middleware throttle para proteção rápida em rotas, e a facade RateLimiter para controle programático mais refinado.
Configuração Inicial e Middleware throttle
O middleware throttle é a forma mais simples de aplicar rate limiting. Sua sintaxe básica é throttle:60,1, que significa 60 requisições a cada 1 minuto.
// Em routes/web.php ou routes/api.php
Route::get('/api/users', function () {
return User::all();
})->middleware('throttle:60,1');
Para grupos de rotas, aplique o middleware no grupo inteiro:
Route::middleware('throttle:100,1')->group(function () {
Route::get('/api/posts', [PostController::class, 'index']);
Route::post('/api/posts', [PostController::class, 'store']);
});
Você também pode usar nomes de limitadores pré-definidos. O Laravel já inclui api e login como limitadores padrão:
// Aplica o limitador 'api' configurado no AppServiceProvider
Route::middleware('throttle:api')->group(function () {
// ...
});
Rate Limiter com a Facade RateLimiter
Para controle mais granular, use a facade Illuminate\Support\Facades\RateLimiter. Seus métodos principais incluem:
attempt(): Executa uma ação se o limite não foi atingidotooManyAttempts(): Verifica se o limite foi excedidohit(): Incrementa o contador de tentativasclear(): Reseta o contador para uma chave específica
Exemplo prático: limitar envio de e-mails por usuário a 5 por hora:
use Illuminate\Support\Facades\RateLimiter;
public function sendEmail(Request $request)
{
$key = 'send-email:' . $request->user()->id;
$executed = RateLimiter::attempt(
$key,
$maxAttempts = 5,
function() use ($request) {
// Lógica de envio de e-mail
Mail::to($request->user())->send(new WelcomeMail());
return response()->json(['message' => 'E-mail enviado']);
},
$decaySeconds = 3600 // 1 hora
);
if (! $executed) {
$seconds = RateLimiter::availableIn($key);
return response()->json([
'message' => 'Muitas tentativas. Tente novamente em ' . $seconds . ' segundos.',
'retry_after' => $seconds
], 429);
}
return $executed;
}
Limitadores Nomeados e Definição no AppServiceProvider
Registre limitadores personalizados no método boot() do AppServiceProvider usando RateLimiter::for():
// app/Providers/AppServiceProvider.php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;
public function boot()
{
RateLimiter::for('uploads', function (Request $request) {
return Limit::perMinute(10)
->by($request->user()?->id ?: $request->ip());
});
RateLimiter::for('login', function (Request $request) {
return Limit::perMinute(5)
->by($request->input('email') . '|' . $request->ip())
->response(function () {
return response('Muitas tentativas de login.', 429);
});
});
}
Para usar esses limitadores nas rotas:
Route::post('/upload', [UploadController::class, 'store'])
->middleware('throttle:uploads');
Route::post('/login', [AuthController::class, 'login'])
->middleware('throttle:login');
Segmentação por Usuário e IP
Uma das vantagens dos limitadores nomeados é a segmentação dinâmica. O método by() define como identificar cada cliente. Exemplo: 100 requisições/min para usuários logados, 10 para anônimos:
RateLimiter::for('api', function (Request $request) {
$user = $request->user();
if ($user) {
return Limit::perMinute(100)->by($user->id);
}
return Limit::perMinute(10)->by($request->ip());
});
Respostas Personalizadas e Headers HTTP
Por padrão, o Laravel retorna uma resposta 429 com JSON. Para personalizar, use o método response() no limitador:
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->response(function (Request $request, array $headers) {
return response('Limite excedido. Tente novamente mais tarde.', 429, $headers);
});
});
Para incluir headers HTTP de rate limiting, o Laravel já adiciona automaticamente X-RateLimit-Limit, X-RateLimit-Remaining e Retry-After. Você pode acessá-los na resposta:
$response = response()->json(['message' => 'OK']);
$response->header('X-RateLimit-Limit', 60);
$response->header('X-RateLimit-Remaining', RateLimiter::remaining($key));
return $response;
Para tratamento avançado com exceções:
use Illuminate\Http\Exceptions\ThrottleRequestsException;
public function handleRateLimit(Request $request, Closure $next)
{
if (RateLimiter::tooManyAttempts($key, 5)) {
throw new ThrottleRequestsException('Muitas requisições');
}
return $next($request);
}
Testes e Cache de Rate Limiting
Em testes, use RateLimiter::clear() para resetar os contadores entre cenários:
public function test_rate_limit()
{
RateLimiter::clear('send-email:' . $user->id);
// Simula 5 requisições
for ($i = 0; $i < 5; $i++) {
$response = $this->post('/send-email');
$response->assertStatus(200);
}
// A 6ª deve ser bloqueada
$response = $this->post('/send-email');
$response->assertStatus(429);
}
Para desempenho em produção, configure o cache driver para Redis em vez de file:
CACHE_DRIVER=redis
REDIS_CLIENT=predis
O Redis oferece operações atômicas e melhor performance para contadores de rate limiting em alta concorrência.
Boas Práticas e Casos de Uso Avançados
Limitação por endpoint específico: Endpoints críticos como login e upload devem ter limites mais restritivos:
Route::post('/login', [AuthController::class, 'login'])
->middleware('throttle:5,1'); // 5 tentativas por minuto
Route::post('/upload-avatar', [UploadController::class, 'avatar'])
->middleware('throttle:3,10'); // 3 uploads a cada 10 minutos
Integração com filas: Para operações lentas que disparam jobs, use rate limiting para evitar sobrecarga no processamento:
public function processBatch()
{
$key = 'batch-process:' . auth()->id();
if (RateLimiter::tooManyAttempts($key, 2)) {
return back()->with('error', 'Processamento já está em andamento.');
}
RateLimiter::hit($key, 60);
ProcessBatchJob::dispatch();
}
Estratégias de backoff exponencial: Combine rate limiting com notificações ao usuário:
if (RateLimiter::tooManyAttempts($key, 5)) {
$seconds = RateLimiter::availableIn($key);
$waitMinutes = ceil($seconds / 60);
Log::warning('Rate limit excedido para usuário ' . $user->id);
$user->notify(new RateLimitExceededNotification($waitMinutes));
return response('Limite excedido. Tente novamente em ' . $waitMinutes . ' minutos.', 429);
}
Implementar rate limiting e throttling no Laravel não é apenas uma questão de segurança, mas também de qualidade de serviço. Com as ferramentas nativas do framework, você pode proteger sua aplicação de forma elegante e eficiente, garantindo que recursos sejam distribuídos de maneira justa entre todos os usuários.
Referências
- Documentação oficial do Laravel sobre Rate Limiting — Guia completo sobre configuração de rate limiting no Laravel, incluindo middleware throttle e facade RateLimiter.
- Laravel News - Understanding Rate Limiting — Artigo técnico com exemplos práticos de implementação de rate limiting em APIs Laravel.
- Laracasts - Rate Limiting in Laravel — Tutorial em vídeo sobre como configurar e personalizar rate limiting no Laravel.
- Laravel Daily - Rate Limiting Best Practices — Artigo com boas práticas e casos de uso avançados de rate limiting.
- Laravel API - RateLimiter Facade — Documentação técnica completa da facade RateLimiter com todos os métodos disponíveis.
- Spatie - Laravel Rate Limiting Package — Pacote complementar para rate limiting avançado com suporte a Redis e backoff exponencial.