Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Por Arvin Kahbazi, Maarten Balliauw e Rick Anderson
O middleware Microsoft.AspNetCore.RateLimiting
fornece middleware de limitação de taxa. Os aplicativos configuram políticas de limitação de taxa de transferência e anexam as políticas aos endpoints. Os aplicativos que usam a limitação de taxa devem ser cuidadosamente testados e revisados antes da implantação. Confira Testando pontos de extremidade com limitação de taxa neste artigo para obter mais informações.
Para obter uma introdução à limitação de taxa, confira Middleware de limitação de taxa.
Por que usar limitação de taxa
A limitação de taxa pode ser usada para gerenciar o fluxo de solicitações de entrada para um aplicativo. Principais motivos para implementar a limitação de taxa:
- Prevenção de abuso: a limitação de taxa ajuda a proteger um aplicativo contra abusos limitando o número de solicitações que um usuário ou cliente pode fazer em um determinado período de tempo. Isso é particularmente importante para APIs públicas.
- Garantindo o uso justo: ao definir limites, todos os usuários têm acesso justo aos recursos, impedindo que os usuários monopolizem o sistema.
- Proteção de recursos: a limitação de taxa ajuda a evitar a sobrecarga do servidor controlando o número de solicitações que podem ser processadas, protegendo assim que os recursos de back-end sejam sobrecarregados.
- Aprimoramento da segurança: ele pode reduzir o risco de ataques de DoS (Negação de Serviço) limitando a taxa na qual as solicitações são processadas, tornando mais difícil para os invasores inundarem um sistema.
- Melhorando o desempenho: ao controlar a taxa de solicitações de entrada, o desempenho ideal e a capacidade de resposta de um aplicativo podem ser mantidos, garantindo uma melhor experiência do usuário.
- Gerenciamento de Custos: para serviços que incorrem em custos com base no uso, a limitação de taxa pode ajudar a gerenciar e prever despesas controlando o volume de solicitações processadas.
Implementar a limitação de taxa em um aplicativo ASP.NET Core pode ajudar a manter a estabilidade, a segurança e o desempenho, garantindo um serviço confiável e eficiente para todos os usuários.
Prevenção de ataques de DDoS
Embora a limitação de taxa possa ajudar a reduzir o risco de ataques de DoS (Negação de Serviço) limitando a taxa na qual as solicitações são processadas, não é uma solução abrangente para ataques de DDoS (Negação de Serviço Distribuído). Os ataques de DDoS envolvem vários sistemas sobrecarregando um aplicativo com uma enxurrada de solicitações, dificultando o processamento apenas com limitação de taxa.
Para uma proteção DDoS robusta, considere o uso de um serviço de proteção contra DDoS comercial. Esses serviços oferecem recursos avançados, como:
- Análise de Tráfego: monitoramento contínuo e análise do tráfego de entrada para detectar e atenuar ataques de DDoS em tempo real.
- Escalabilidade: a capacidade de lidar com ataques em larga escala distribuindo tráfego entre vários servidores e data centers.
- Mitigação Automatizada: mecanismos de resposta automatizados para bloquear rapidamente o tráfego mal-intencionado sem intervenção manual.
- Rede Global: uma rede global de servidores para absorver e mitigar ataques mais próximos da origem.
- Atualizações constantes: os serviços comerciais rastreiam e atualizam continuamente seus mecanismos de proteção para se adaptarem a ameaças novas e em evolução.
Ao usar um serviço de hospedagem na nuvem, a proteção contra DDoS geralmente está disponível como parte da solução de hospedagem, como o Firewall de Aplicativo Web do Azure, o AWS Shield ou o Google Cloud Armor. As proteções dedicadas estão disponíveis como WAF (Firewalls de Aplicativo Web) ou como parte de uma solução cdn, como Cloudflare ou Akamai Kona Site Defender
Implementar um serviço de proteção contra DDoS comercial em conjunto com a limitação de taxa pode fornecer uma estratégia de defesa abrangente, garantindo a estabilidade, a segurança e o desempenho de um aplicativo.
Usar middleware de limitação de taxa
As etapas a seguir mostram como usar o middleware de limitação de taxa em um aplicativo ASP.NET Core:
- Configurar serviços de limitação de taxa.
No arquivo Program.cs
, configure os serviços de limitação de taxa adicionando as políticas apropriadas de limitação de taxa. As políticas podem ser definidas como globais ou nomeadas. O exemplo a seguir permite 10 solicitações por minuto por usuário (identidade) ou globalmente:
builder.Services.AddRateLimiter(options =>
{
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
RateLimitPartition.GetFixedWindowLimiter(
partitionKey: httpContext.User.Identity?.Name ?? httpContext.Request.Headers.Host.ToString(),
factory: partition => new FixedWindowRateLimiterOptions
{
AutoReplenishment = true,
PermitLimit = 10,
QueueLimit = 0,
Window = TimeSpan.FromMinutes(1)
}));
});
As políticas nomeadas precisam ser aplicadas explicitamente às páginas ou aos pontos de extremidade. O exemplo a seguir adiciona uma política de limitação de janela fixa chamada "fixed"
, que vamos adicionar a um endpoint posteriormente.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter("fixed", opt =>
{
opt.PermitLimit = 4;
opt.Window = TimeSpan.FromSeconds(12);
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
opt.QueueLimit = 2;
});
});
var app = builder.Build();
O limitador global se aplica a todos os pontos de extremidade automaticamente quando ele é configurado por meio de options.GlobalLimiter.
Habilitar middleware de limitação de taxa
Program.cs
No arquivo, habilite o middleware de limitação de taxa chamando UseRateLimiter:
app.UseRouting();
app.UseRateLimiter();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run();
Aplicar políticas de limitação de taxa a pontos de extremidade ou páginas
Aplicar limitação de taxa aos pontos de extremidade da WebAPI
Aplique uma política nomeada ao ponto de extremidade ou grupo, por exemplo:
app.MapGet("/api/resource", () => "This endpoint is rate limited")
.RequireRateLimiting("fixed"); // Apply specific policy to an endpoint
Aplicar limitação de taxa aos controladores MVC
Aplique as políticas de limitação de taxa configuradas a pontos de extremidade específicos ou globalmente. Por exemplo, para aplicar a política "fixa" a todos os pontos de extremidade do controlador:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers().RequireRateLimiting("fixed");
});
Aplicar limitação de taxa aos aplicativos Blazor no lado do servidor
Para definir a limitação de taxa para todos os componentes roteáveis Razor do aplicativo, especifique RequireRateLimiting com o nome da política de limitação de taxa na chamada MapRazorComponents no arquivo Program
. No exemplo a seguir, a política de limitação de taxa chamada "policy
" é aplicada:
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.RequireRateLimiting("policy");
Para definir uma política para um único componente roteável Razor ou uma pasta de componentes por meio de um _Imports.razor
arquivo, o [EnableRateLimiting]
atributo é aplicado com o nome da política. No exemplo a seguir, a política de limitação de taxa chamada "override
" é aplicada. A política substituirá qualquer política atualmente aplicada ao endpoint. O limitador global ainda é executado no endpoint com esse atributo aplicado.
@page "/counter"
@using Microsoft.AspNetCore.RateLimiting
@attribute [EnableRateLimiting("override")]
<h1>Counter</h1>
O [EnableRateLimiting]
atributo só é aplicado a um componente roteável ou a uma pasta de componentes por meio de um _Imports.razor
arquivo se RequireRateLimiting não for chamado em MapRazorComponents.
O [DisableRateLimiting]
atributo é usado para desabilitar a limitação de taxa para um componente roteável ou uma pasta de componentes por meio de um _Imports.razor
arquivo.
Algoritmos de limitador de taxa
A classe RateLimiterOptionsExtensions
fornece os seguintes métodos de extensão para limitação de taxa:
Os limitadores fixos, deslizantes e de token limitam o número máximo de solicitações em um período de tempo. O limitador de simultaneidade limita apenas o número de solicitações simultâneas e não limita o número de solicitações em um período de tempo. O custo de um ponto de extremidade deve ser considerado ao selecionar um limitador. O custo de um endpoint inclui os recursos usados – por exemplo, tempo, acesso a dados, CPU e E/S.
Limitador de janela fixa
O método AddFixedWindowLimiter
usa uma janela de tempo fixa para limitar as solicitações. Quando a janela de tempo expira, uma nova janela de tempo é iniciada e o limite de solicitação é redefinido.
Considere o seguinte código:
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRateLimiter(_ => _
.AddFixedWindowLimiter(policyName: "fixed", options =>
{
options.PermitLimit = 4;
options.Window = TimeSpan.FromSeconds(12);
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = 2;
}));
var app = builder.Build();
app.UseRateLimiter();
static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");
app.MapGet("/", () => Results.Ok($"Hello {GetTicks()}"))
.RequireRateLimiting("fixed");
app.Run();
O código anterior:
- Chama AddRateLimiter para adicionar um serviço de limitação de taxa à coleção de serviços.
- Chama
AddFixedWindowLimiter
para criar um limitador de janela fixa com um nome de política"fixed"
e define: - PermitLimit como 4 e o tempo Window como 12. Um máximo de 4 solicitações por cada janela de 12 segundos são permitidas.
- QueueProcessingOrder às OldestFirst.
- QueueLimit para 2 (defina como 0 para desabilitar o mecanismo de fila).
- Chama UseRateLimiter para habilitar a limitação de taxa.
Os aplicativos devem usar Configuração para definir opções de limitador. O código a seguir atualiza o código anterior usando MyRateLimitOptions
para configuração:
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.RateLimiting;
using WebRateLimitAuth.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<MyRateLimitOptions>(
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit));
var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
var fixedPolicy = "fixed";
builder.Services.AddRateLimiter(_ => _
.AddFixedWindowLimiter(policyName: fixedPolicy, options =>
{
options.PermitLimit = myOptions.PermitLimit;
options.Window = TimeSpan.FromSeconds(myOptions.Window);
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
}));
var app = builder.Build();
app.UseRateLimiter();
static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");
app.MapGet("/", () => Results.Ok($"Fixed Window Limiter {GetTicks()}"))
.RequireRateLimiting(fixedPolicy);
app.Run();
UseRateLimiter deve ser chamado após UseRouting
quando as APIs específicas do ponto de extremidade de limitação de taxa são usadas. Por exemplo, se o atributo [EnableRateLimiting]
for usado, UseRateLimiter
deverá ser chamado após UseRouting
. Ao chamar apenas limitadores globais, UseRateLimiter
pode ser chamado antes de UseRouting
.
Limitador de janela deslizante
Um algoritmo de janela deslizante:
- É semelhante ao limitador de janela fixa, mas adiciona segmentos por janela. A janela desliza um segmento a cada intervalo de segmento. O intervalo do segmento é (tempo da janela)/(segmentos por janela).
- Limita as solicitações de uma janela a solicitações
permitLimit
. - Cada janela de tempo é dividida em
n
segmentos por janela. - As solicitações retiradas do segmento de tempo expirado de uma janela (
n
segmentos anteriores ao segmento atual) são adicionadas ao segmento atual. Nos referimos ao segmento de tempo mais expirado uma janela de volta como o segmento expirado.
Considere a tabela a seguir, que mostra um limitador de janela deslizante com uma janela de 30 segundos, três segmentos por janela e um limite de 100 solicitações:
- A primeira linha e a primeira coluna mostram o segmento de tempo.
- A segunda linha mostra as solicitações restantes disponíveis. As solicitações restantes são calculadas como as solicitações disponíveis menos as solicitações processadas mais as solicitações recicladas.
- As solicitações em cada vez se movem ao longo da linha azul diagonal.
- A partir do tempo 30, as solicitações retiradas do segmento de tempo expirado são adicionadas novamente ao limite de solicitações, conforme mostrado nas linhas vermelhas.
A tabela a seguir mostra os dados no grafo anterior em um formato diferente. A coluna Disponível mostra as solicitações disponíveis do segmento anterior (O Transporte da linha anterior). A primeira linha mostra 100 solicitações disponíveis porque não há nenhum segmento anterior.
Hora | Disponível | Obtido | Reciclado de expirado | Carregar |
---|---|---|---|---|
0 | 100 | 20 | 0 | 80 |
10 | 80 | 30 | 0 | 50 |
20 | 50 | 40 | 0 | 10 |
30 | 10 | 30 | 20 | 0 |
40 | 0 | 10 | 30 | 20 |
50 | 20 | 10 | 40 | 50 |
60 | 50 | 35 | 30 | 45 |
O código a seguir usa o limitador de taxa de janela deslizante:
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;
var builder = WebApplication.CreateBuilder(args);
var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
var slidingPolicy = "sliding";
builder.Services.AddRateLimiter(_ => _
.AddSlidingWindowLimiter(policyName: slidingPolicy, options =>
{
options.PermitLimit = myOptions.PermitLimit;
options.Window = TimeSpan.FromSeconds(myOptions.Window);
options.SegmentsPerWindow = myOptions.SegmentsPerWindow;
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
}));
var app = builder.Build();
app.UseRateLimiter();
static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");
app.MapGet("/", () => Results.Ok($"Sliding Window Limiter {GetTicks()}"))
.RequireRateLimiting(slidingPolicy);
app.Run();
Limitador de bucket de token
O limitador de bucket de token é semelhante ao limitador de janela deslizante, mas em vez de adicionar de volta as solicitações feitas do segmento expirado, um número fixo de tokens é adicionado a cada período de reabastecimento. Os tokens adicionados a cada segmento não podem aumentar os tokens disponíveis para um número maior que o limite do bucket de token. A tabela a seguir mostra um limitador de bucket de token com um limite de 100 tokens e um período de reabastecimento de 10 segundos.
Hora | Disponível | Obtido | Adicionado | Carregar |
---|---|---|---|---|
0 | 100 | 20 | 0 | 80 |
10 | 80 | 10 | 20 | 90 |
20 | 90 | 5 | 15 | 100 |
30 | 100 | 30 | 20 | 90 |
40 | 90 | 6 | 16 | 100 |
50 | 100 | 40 | 20 | 80 |
60 | 80 | 50 | 20 | 50 |
O código a seguir usa o limitador de bucket de token:
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;
var builder = WebApplication.CreateBuilder(args);
var tokenPolicy = "token";
var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
builder.Services.AddRateLimiter(_ => _
.AddTokenBucketLimiter(policyName: tokenPolicy, options =>
{
options.TokenLimit = myOptions.TokenLimit;
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
options.ReplenishmentPeriod = TimeSpan.FromSeconds(myOptions.ReplenishmentPeriod);
options.TokensPerPeriod = myOptions.TokensPerPeriod;
options.AutoReplenishment = myOptions.AutoReplenishment;
}));
var app = builder.Build();
app.UseRateLimiter();
static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");
app.MapGet("/", () => Results.Ok($"Token Limiter {GetTicks()}"))
.RequireRateLimiting(tokenPolicy);
app.Run();
Quando AutoReplenishment é definido como true
, um temporizador interno reabastece os tokens a cada ReplenishmentPeriod; quando definido como false
, o aplicativo deve chamar TryReplenish no limitador.
Limitador de concorrência
O limitador de simultaneidade limita o número de solicitações simultâneas. Cada solicitação reduz o limite de simultaneidade em um. Quando uma solicitação é concluída, o limite é aumentado em um. Ao contrário dos outros limitadores de solicitações que limitam o número total de solicitações para um período especificado, o limitador de simultaneidade limita apenas o número de solicitações simultâneas e não limita o número de solicitações em um período de tempo.
O código a seguir usa o limitador de simultaneidade:
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;
var builder = WebApplication.CreateBuilder(args);
var concurrencyPolicy = "Concurrency";
var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
builder.Services.AddRateLimiter(_ => _
.AddConcurrencyLimiter(policyName: concurrencyPolicy, options =>
{
options.PermitLimit = myOptions.PermitLimit;
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
}));
var app = builder.Build();
app.UseRateLimiter();
static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");
app.MapGet("/", async () =>
{
await Task.Delay(500);
return Results.Ok($"Concurrency Limiter {GetTicks()}");
}).RequireRateLimiting(concurrencyPolicy);
app.Run();
Partições de limitação de taxa
As partições de limitação de taxa dividem o tráfego em "baldes" separados, cada um com seus próprios contadores de limite de taxa. Isso permite um controle mais granular do que um único contador global. Os "buckets" de partição são definidos por chaves diferentes (como ID de usuário, endereço IP ou chave de API).
Benefícios do particionamento
- Equidade: um usuário não pode consumir todo o limite de taxa para os demais usuários
- Granularidade: limites diferentes para diferentes usuários/recursos
- Segurança: Melhor proteção contra abuso direcionado
- Serviço em camadas: suporte para camadas de serviço com limites diferentes
A limitação de taxa particionada oferece controle refinado sobre como você gerencia o tráfego de API, garantindo uma alocação justa de recursos.
Por endereço IP
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
RateLimitPartition.GetFixedWindowLimiter(
partitionKey: httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown",
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 50,
Window = TimeSpan.FromMinutes(1)
}));
Por usuário Identity
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
RateLimitPartition.GetFixedWindowLimiter(
partitionKey: httpContext.User.Identity?.Name ?? "anonymous",
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 100,
Window = TimeSpan.FromMinutes(1)
}));
Por chave de API
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
{
string apiKey = httpContext.Request.Headers["X-API-Key"].ToString() ?? "no-key";
// Different limits based on key tier
return apiKey switch
{
"premium-key" => RateLimitPartition.GetFixedWindowLimiter(
partitionKey: apiKey,
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 1000,
Window = TimeSpan.FromMinutes(1)
}),
_ => RateLimitPartition.GetFixedWindowLimiter(
partitionKey: apiKey,
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 100,
Window = TimeSpan.FromMinutes(1)
}),
};
});
Por caminho do ponto de extremidade
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
{
string path = httpContext.Request.Path.ToString();
// Different limits for different paths
if (path.StartsWith("/api/public"))
{
return RateLimitPartition.GetFixedWindowLimiter(
partitionKey: $"{httpContext.Connection.RemoteIpAddress}-public",
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 30,
Window = TimeSpan.FromSeconds(10)
});
}
return RateLimitPartition.GetFixedWindowLimiter(
partitionKey: httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown",
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 100,
Window = TimeSpan.FromMinutes(1)
});
});
Criar limitadores encadeados
A API CreateChained permite passar vários PartitionedRateLimiter que são combinados em um PartitionedRateLimiter
. O limitador combinado executa todos os limitadores de entrada em sequência.
O código a seguir usa CreateChained
:
using System.Globalization;
using System.Threading.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRateLimiter(_ =>
{
_.OnRejected = async (context, cancellationToken) =>
{
if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
{
context.HttpContext.Response.Headers.RetryAfter =
((int) retryAfter.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo);
}
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
await context.HttpContext.Response.WriteAsync("Too many requests. Please try again later.", cancellationToken);
};
_.GlobalLimiter = PartitionedRateLimiter.CreateChained(
PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
{
var userAgent = httpContext.Request.Headers.UserAgent.ToString();
return RateLimitPartition.GetFixedWindowLimiter
(userAgent, _ =>
new FixedWindowRateLimiterOptions
{
AutoReplenishment = true,
PermitLimit = 4,
Window = TimeSpan.FromSeconds(2)
});
}),
PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
{
var userAgent = httpContext.Request.Headers.UserAgent.ToString();
return RateLimitPartition.GetFixedWindowLimiter
(userAgent, _ =>
new FixedWindowRateLimiterOptions
{
AutoReplenishment = true,
PermitLimit = 20,
Window = TimeSpan.FromSeconds(30)
});
}));
});
var app = builder.Build();
app.UseRateLimiter();
static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");
app.MapGet("/", () => Results.Ok($"Hello {GetTicks()}"));
app.Run();
Para obter mais informações, confira o código-fonte CreateChained
Escolha do que acontece quando uma solicitação é limitada por taxa
Para casos simples, basta definir o código de status:
builder.Services.AddRateLimiter(options =>
{
// Set a custom status code for rejections
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
// Rate limiter configuration...
});
A abordagem mais comum é registrar uma chamada de retorno OnRejected ao configurar a limitação de taxa:
builder.Services.AddRateLimiter(options =>
{
// Rate limiter configuration...
options.OnRejected = async (context, cancellationToken) =>
{
// Custom rejection handling logic
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
context.HttpContext.Response.Headers["Retry-After"] = "60";
await context.HttpContext.Response.WriteAsync("Rate limit exceeded. Please try again later.", cancellationToken);
// Optional logging
logger.LogWarning("Rate limit exceeded for IP: {IpAddress}",
context.HttpContext.Connection.RemoteIpAddress);
};
});
Outra opção é enfileirar a solicitação:
Enfileiramento de solicitações
Com a fila habilitada, quando uma solicitação excede o limite de taxa, ela é colocada em uma fila em que a solicitação aguarda até que uma permissão fique disponível ou até que ocorra um tempo limite. As solicitações são processadas de acordo com uma ordem de fila configurável.
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter("api", options =>
{
options.PermitLimit = 10; // Allow 10 requests
options.Window = TimeSpan.FromSeconds(10); // Per 10-second window
options.QueueLimit = 5; // Queue up to 5 additional requests
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; // Process oldest requests first
options.AutoReplenishment = true; // Default: automatically replenish permits
});
});
Atributos EnableRateLimiting
e DisableRateLimiting
Os atributos [EnableRateLimiting]
e [DisableRateLimiting]
podem ser aplicados a um Controlador, método de ação ou página Razor. Para páginas Razor, o atributo deve ser aplicado à página Razor e não aos manipuladores de página. Por exemplo, [EnableRateLimiting]
não pode ser aplicado a OnGet
, OnPost
ou a qualquer outro manipulador de página.
O atributo [DisableRateLimiting]
desabilita a limitação de taxa ao Controlador, ao método de ação ou à página Razor, independentemente dos limitadores de taxa nomeados ou dos limitadores globais aplicados. Por exemplo, considere o seguinte código que chama RequireRateLimiting para aplicar a limitação de taxa fixedPolicy
a todos os endpoints do controlador.
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.Configure<MyRateLimitOptions>(
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit));
var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
var fixedPolicy = "fixed";
builder.Services.AddRateLimiter(_ => _
.AddFixedWindowLimiter(policyName: fixedPolicy, options =>
{
options.PermitLimit = myOptions.PermitLimit;
options.Window = TimeSpan.FromSeconds(myOptions.Window);
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
}));
var slidingPolicy = "sliding";
builder.Services.AddRateLimiter(_ => _
.AddSlidingWindowLimiter(policyName: slidingPolicy, options =>
{
options.PermitLimit = myOptions.SlidingPermitLimit;
options.Window = TimeSpan.FromSeconds(myOptions.Window);
options.SegmentsPerWindow = myOptions.SegmentsPerWindow;
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
}));
var app = builder.Build();
app.UseRateLimiter();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.MapRazorPages().RequireRateLimiting(slidingPolicy);
app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy);
app.Run();
No código a seguir, [DisableRateLimiting]
desabilita a limitação de taxa e substitui o [EnableRateLimiting("fixed")]
aplicado ao Home2Controller
e app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy)
chamados em Program.cs
:
[EnableRateLimiting("fixed")]
public class Home2Controller : Controller
{
private readonly ILogger<Home2Controller> _logger;
public Home2Controller(ILogger<Home2Controller> logger)
{
_logger = logger;
}
public ActionResult Index()
{
return View();
}
[EnableRateLimiting("sliding")]
public ActionResult Privacy()
{
return View();
}
[DisableRateLimiting]
public ActionResult NoLimit()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
No código anterior, o [EnableRateLimiting("sliding")]
não é aplicado ao método de ação Privacy
porque Program.cs
chamou app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy)
.
Considere o seguinte código que não chama RequireRateLimiting
em MapRazorPages
ou MapDefaultControllerRoute
:
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.Configure<MyRateLimitOptions>(
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit));
var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
var fixedPolicy = "fixed";
builder.Services.AddRateLimiter(_ => _
.AddFixedWindowLimiter(policyName: fixedPolicy, options =>
{
options.PermitLimit = myOptions.PermitLimit;
options.Window = TimeSpan.FromSeconds(myOptions.Window);
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
}));
var slidingPolicy = "sliding";
builder.Services.AddRateLimiter(_ => _
.AddSlidingWindowLimiter(policyName: slidingPolicy, options =>
{
options.PermitLimit = myOptions.SlidingPermitLimit;
options.Window = TimeSpan.FromSeconds(myOptions.Window);
options.SegmentsPerWindow = myOptions.SegmentsPerWindow;
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
}));
var app = builder.Build();
app.UseRateLimiter();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.MapRazorPages();
app.MapDefaultControllerRoute(); // RequireRateLimiting not called
app.Run();
Considere o seguinte controlador:
[EnableRateLimiting("fixed")]
public class Home2Controller : Controller
{
private readonly ILogger<Home2Controller> _logger;
public Home2Controller(ILogger<Home2Controller> logger)
{
_logger = logger;
}
public ActionResult Index()
{
return View();
}
[EnableRateLimiting("sliding")]
public ActionResult Privacy()
{
return View();
}
[DisableRateLimiting]
public ActionResult NoLimit()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
No controlador anterior:
- O limitador de taxa de política
"fixed"
é aplicado a todos os métodos de ação que não têm atributosEnableRateLimiting
eDisableRateLimiting
. - O limitador de taxa de política
"sliding"
é aplicado à açãoPrivacy
. - A limitação de taxa está desabilitada no método de ação
NoLimit
.
Métricas de limitação de taxa
O middleware de limitação de taxa fornece métricas internas e recursos de monitoramento para ajudar a entender como os limites de taxa estão afetando o desempenho do aplicativo e a experiência do usuário. Consulte Microsoft.AspNetCore.RateLimiting
para uma lista de métricas.
Testando pontos de extremidade com limitação de taxa
Antes de implantar em produção um aplicativo com limitação de taxa, teste o aplicativo para validar os limitadores de taxa e as opções usadas. Por exemplo, crie um script JMeter com uma ferramenta como o BlazeMeter ou o Gravador de Script de Teste HTTP(S) do Apache JMeter e carregue o script no Teste de Carga do Azure.
A criação de partições com entrada do usuário torna o aplicativo vulnerável a ataques de Negação de Serviço (DoS). Por exemplo, a criação de partições em endereços IP do cliente torna o aplicativo vulnerável a ataques de negação de serviço que empregam falsificação de endereço IP de origem. Para obter mais informações, confira BCP 38 RFC 2827 Filtragem de Entrada de Rede: prevenção contra ataques de negação de serviço que utilizam falsificação do endereço IP de origem.
Recursos adicionais
- O middleware de limitação de velocidade, de autoria de Maarten Balliauw, oferece uma introdução e visão geral excelentes sobre a limitação de velocidade.
- Limitar a taxa de um manipulador HTTP no .NET