Partilhar via


Middleware de limitação de taxa no ASP.NET Core

Por Arvin Kahbazi, Maarten Balliauwe Rick Anderson

O middleware Microsoft.AspNetCore.RateLimiting fornece middleware de limitação de velocidade. As aplicações configuram políticas de restrição de taxa e, em seguida, associam as políticas aos pontos de extremidade. Os programas que usam limitação de taxa devem ser cuidadosamente testados quanto ao carregamento e revisados antes da implementação. Consulte o artigo Teste de endpoints com limitação de taxa neste artigo para obter mais informações.

Para obter uma introdução à limitação de taxa, consulte Middleware de limitação de taxa.

Por que usar o limite de taxa

O limite de taxa pode ser usado para gerenciar o fluxo de solicitações de entrada para um aplicativo. Principais razões para implementar o limite 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.
  • Garantir o uso justo: Ao estabelecer limites, todos os usuários têm acesso justo aos recursos, evitando que os usuários monopolizem o sistema.
  • Protegendo 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 os recursos de back-end de serem sobrecarregados.
  • Reforçar a segurança: Pode mitigar o risco de ataques de negação de serviço (DoS) ao limitar a taxa de processamento das solicitações, dificultando o trabalho dos atacantes para inundar um sistema.
  • Melhorar o desempenho: Ao controlar a taxa de solicitações recebidas, o desempenho ideal e a capacidade de resposta de um aplicativo podem ser mantidos, garantindo uma melhor experiência do usuário.
  • Cost Management: 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.

A implementação do limite 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 DDoS

Embora a limitação de taxa possa ajudar a mitigar o risco de ataques de negação de serviço (DoS), limitando a taxa na qual as solicitações são processadas, não é uma solução abrangente para ataques distribuídos de negação de serviço (DDoS). Os ataques DDoS envolvem vários sistemas sobrecarregando um aplicativo com uma enxurrada de solicitações, dificultando o tratamento apenas com limitação de taxa.

Para uma proteção robusta contra DDoS, considere o uso de um serviço comercial de proteção contra DDoS. Estes serviços oferecem funcionalidades avançadas, tais como:

  • Análise de Tráfego: Monitoramento e análise contínuos do tráfego de entrada para detetar e mitigar ataques DDoS em tempo real.
  • Escalabilidade: A capacidade de lidar com ataques em grande escala distribuindo o tráfego em vários servidores e data centers.
  • Mitigação Automatizada: Mecanismos de resposta automatizados para bloquear rapidamente o tráfego malicioso sem intervenção manual.
  • Global Network: Uma rede global de servidores para absorver e mitigar ataques mais próximos da fonte.
  • 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 do Azure Web Application Firewall, AWS Shield ou Google Cloud Armor. As proteções dedicadas estão disponíveis como Web Application Firewalls (WAF) ou como parte de uma solução CDN, como Cloudflare ou Akamai Kona Site Defender

A implementação de um serviço comercial de proteção contra DDoS 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:

  1. Configure serviços de limitação de velocidade.

No arquivo Program.cs, configure os serviços de limitação de taxa adicionando as políticas de limitação de taxa apropriadas. As políticas podem ser definidas como políticas 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 explicitamente aplicadas às páginas ou endpoints. O exemplo a seguir adiciona uma política de limitação de janela fixa chamada "fixed", que será adicionada a um ponto de extremidade 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 aplica-se a todos os pontos finais automaticamente quando é configurado através de opções . GlobalLimiter.

  1. Ativar intermediário de limitação de taxa

    No arquivo Program.cs, habilite o middleware de limitação de taxa chamando UseRateLimiter:

app.UseRouting();

app.UseRateLimiter();

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});

app.Run();

Aplique políticas de limitação de taxa a terminais ou páginas

Aplicar limitação de taxa de requisição aos endpoints da API Web

Aplique uma política atribuída 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 endpoints do controlador:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers().RequireRateLimiting("fixed");
});

Aplicar limitação de taxa a aplicações Blazor do lado do servidor

Para definir a limitação de taxa para todos os componentes roteáveis de Razor do aplicativo, especifique RequireRateLimiting com o nome da política de limitação de taxa na chamada de 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 Razor roteável ou uma pasta de componentes por meio de um arquivo _Imports.razor, o atributo [EnableRateLimiting] é 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 substitui 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 atributo [EnableRateLimiting] é aplicado apenas a um componente routável ou a uma pasta de componentes através de um arquivo _Imports.razor, desde que RequireRateLimiting não seja chamado em MapRazorComponents.

O atributo [DisableRateLimiting] é usado para desativar o limite de taxa para um componente roteável ou uma pasta de componentes por meio de um arquivo _Imports.razor.

Algoritmos limitadores de taxa

A classe RateLimiterOptionsExtensions fornece os seguintes métodos de extensão para limitação de taxa:

Os limitadores fixo, deslizante e token limitam o número máximo de requisições num 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 final deve ser considerado ao selecionar um limitador. O custo de um endpoint inclui os recursos utilizados, por exemplo, tempo, acesso a dados, CPU e I/O.

Limitador de janela fixo

O método AddFixedWindowLimiter usa uma janela de tempo fixa para limitar 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 velocidade na coleção de serviços.
  • Chama AddFixedWindowLimiter para criar um limitador de janela fixo com o nome de política "fixed" e define:
  • PermitLimit para 4 e o tempo Window para 12. É permitido um máximo de 4 pedidos por cada janela de 12 segundos.
  • QueueProcessingOrder para OldestFirst.
  • QueueLimit a 2 (defina isto como 0 para desativar o mecanismo de enfileiramento).
  • Invoca UseRateLimiter para ativar a limitação de taxa.

As aplicações 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 APIs específicas de limite 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 fixo, mas adiciona segmentos por janela. A janela desliza um segmento a cada intervalo. O intervalo de segmento é (tempo de janela)/(segmentos por janela).
  • Limita as solicitações para uma janela a permitLimit solicitações.
  • Cada janela de tempo é dividida em n segmentos por janela.
  • As solicitações retiradas do segmento de tempo expirado uma janela atrás (n segmentos anteriores ao segmento atual) são adicionadas ao segmento atual. Referimo-nos ao segmento de tempo mais expirado de uma janela anterior 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 linha superior e a primeira coluna mostram o segmento de tempo.
  • A segunda linha mostra as solicitações restantes disponíveis. Os restantes pedidos são calculados como os pedidos disponíveis menos os pedidos processados mais os pedidos reciclados.
  • As solicitações em cada momento movem-se ao longo da linha azul diagonal.
  • A partir do tempo 30, a solicitação retirada do segmento de tempo expirado é adicionada de volta ao limite de solicitação, conforme mostrado nas linhas vermelhas.

Tabela mostrando solicitações, limites e slots reciclados

A tabela a seguir mostra os dados no gráfico anterior em um formato diferente. A coluna Disponível mostra as solicitações disponíveis do segmento anterior (Os Transportados da linha anterior). A primeira linha mostra 100 solicitações disponíveis porque não há segmento anterior.

Hora Disponível Tomado Reciclado a partir de materiais expirados Transferência
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 com 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 balde de tokens

O limitador de balde de tokens é semelhante ao limitador de janela deslizante, mas, em vez de adicionar novamente as solicitações retiradas 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 superior ao limite do balde de tokens. 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 Tomado Adicionado Transferência
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 está definido como true, um temporizador interno reabastece os tokens a cada ReplenishmentPeriod; quando definido como false, a aplicação deve chamar TryReplenish para o 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 limitadoras de taxa

As partições de limitação de velocidade dividem o tráfego em "buckets" separados, cada um com seus próprios contadores de limite de velocidade. 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

  • Fairness: Um utilizador não pode consumir todo o limite de taxa de utilização para todos
  • Granularidade: Limites diferentes para diferentes utilizadores/recursos
  • Security: Melhor proteção contra abusos direcionados
  • Serviço hierárquico: 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 da 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 Utilizador 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 de ponto final

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 a passagem de vários PartitionedRateLimiter que são combinados num 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, consulte o código-fonte 'CreateChained'

Escolher o que acontece quando um pedido é sujeito a limitação de taxa

Para casos simples, você pode apenas 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 é inscrever uma função 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 Pedidos

Com o enfileiramento habilitado, quando uma solicitação excede o limite de taxa, ela é colocada em uma fila onde 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
    });
});

EnableRateLimiting e DisableRateLimiting atributos

Os atributos [EnableRateLimiting] e [DisableRateLimiting] podem ser aplicados a um controlador, método de ação ou página Razor. Para Razor Pages, o atributo deve ser aplicado ao Razor Page e não aos manipuladores de página. Por exemplo, [EnableRateLimiting] não pode ser aplicado a OnGet, OnPostou qualquer outro manipulador de página.

O atributo [DisableRateLimiting]desabilita limite de taxa para o controlador, método de ação ou página Razor, independentemente dos limitadores de taxa nomeados ou dos limitadores globais aplicados. Por exemplo, considere o código a seguir que chama RequireRateLimiting para aplicar a limitação de taxa de 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] desativa o limite de taxa e substitui o [EnableRateLimiting("fixed")] aplicado ao Home2Controller e ao app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy) chamado 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, [EnableRateLimiting("sliding")] não é aplicado ao método de ação Privacy porque Program.cs chama 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 atributos EnableRateLimiting e DisableRateLimiting.
  • O limitador de taxa de política "sliding" é aplicado à ação Privacy.
  • O limite de taxa está desativado 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 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 obter uma lista de métricas.

Testes de endpoints com limitação de taxa de transferência

Antes de implantar uma aplicação com limitação de taxa no ambiente de produção, realize um teste de stress na aplicação para validar os limitadores de taxa e as opções utilizadas. Por exemplo, crie um script JMeter com uma ferramenta como BlazeMeter ou Apache JMeter HTTP(S) Test Script Recorder e carregue o script em Teste de Carga do Azure.

A criação de partições a partir da inserção do utilizador torna o aplicativo vulnerável a ataques de de negação de serviço (DoS). Por exemplo, a criação de partições em endereços IP de clientes torna o aplicativo vulnerável a ataques de negação de serviço que empregam falsificação de endereços de origem IP. Para obter mais informações, consulte BCP 38 RFC 2827 Network Ingress Filtering: Defeating Denial of Service Attacks que empregam IP Source Address Spoofing.

Recursos adicionais