Condividi tramite


Middleware di limitazione della frequenza in ASP.NET Core

Di Arvin Kahbazi, Maarten Balliauw e Rick Anderson

Il Microsoft.AspNetCore.RateLimiting middleware fornisce middleware per la limitazione della velocità. Le app configurano i criteri di limitazione della frequenza e quindi allegano i criteri agli endpoint. Le app che usano la limitazione della frequenza devono essere testate e esaminate attentamente prima della distribuzione. Per ulteriori informazioni, vedere Test degli endpoint con limitazione della frequenza in questo articolo.

Per un'introduzione alla limitazione della frequenza, vedere Middleware di limitazione della frequenza.

Perché usare la limitazione della frequenza

La limitazione della frequenza può essere usata per gestire il flusso delle richieste in ingresso a un'app. Motivi principali per implementare la limitazione della frequenza:

  • prevenzione degli abusi: la limitazione della frequenza consente di proteggere un'app da abusi limitando il numero di richieste che un utente o un client può effettuare in un determinato periodo di tempo. Ciò è particolarmente importante per le API pubbliche.
  • garantire un utilizzo equo: impostando limiti, tutti gli utenti hanno accesso equo alle risorse, impedendo agli utenti di monopolizzare il sistema.
  • Protezione delle risorse: la limitazione della frequenza consente di impedire l'overload del server controllando il numero di richieste che possono essere elaborate, proteggendo così le risorse back-end da sovraccaricare.
  • miglioramento della sicurezza: può ridurre il rischio di attacchi Denial of Service (DoS) limitando la frequenza con cui vengono elaborate le richieste, rendendo più difficile per gli utenti malintenzionati inondare un sistema.
  • Migliorare le prestazioni: controllando la frequenza delle richieste in ingresso, è possibile mantenere prestazioni ottimali e velocità di risposta di un'app, garantendo un'esperienza utente migliore.
  • Gestione costi: per i servizi che comportano costi in base all'utilizzo, la limitazione della frequenza può aiutare a gestire e stimare le spese controllando il volume delle richieste elaborate.

L'implementazione della limitazione della frequenza in un'app ASP.NET Core consente di mantenere stabilità, sicurezza e prestazioni, garantendo un servizio affidabile ed efficiente per tutti gli utenti.

Prevenzione degli attacchi DDoS

Anche se la limitazione della frequenza può contribuire a ridurre il rischio di attacchi Denial of Service (DoS) limitando la frequenza con cui vengono elaborate le richieste, non è una soluzione completa per gli attacchi Distributed Denial of Service (DDoS). Gli attacchi DDoS coinvolgono più sistemi, sovraccaricando un'app con un'inondazione di richieste, rendendo difficile gestirla con la sola limitazione del tasso di richiesta.

Per una protezione DDoS affidabile, è consigliabile usare un servizio di protezione DDoS commerciale. Questi servizi offrono funzionalità avanzate, ad esempio:

  • Analisi del traffico: monitoraggio e analisi continui del traffico in ingresso per rilevare e attenuare gli attacchi DDoS in tempo reale.
  • scalabilità: possibilità di gestire attacchi su larga scala distribuendo il traffico tra più server e data center.
  • di mitigazione automatizzata: meccanismi di risposta automatizzati per bloccare rapidamente il traffico dannoso senza intervento manuale.
  • rete globale: una rete globale di server per assorbire e attenuare gli attacchi più vicini all'origine.
  • aggiornamenti costanti: i servizi commerciali tengono costantemente traccia e aggiornano i meccanismi di protezione per adattarsi alle minacce nuove e in continua evoluzione.

Quando si usa un servizio di hosting cloud, la protezione DDoS è in genere disponibile come parte della soluzione di hosting, ad esempio Web Application Firewall di Azure, AWS Shield o Google Cloud Armor. Le protezioni dedicate sono disponibili come web application firewall (WAF) o come parte di una soluzione CDN, ad esempio Cloudflare o Akamai Kona Site Defender

L'implementazione di un servizio di protezione DDoS commerciale in combinazione con la limitazione della frequenza può fornire una strategia di difesa completa, garantendo stabilità, sicurezza e prestazioni di un'app.

Usare il middleware per la limitazione della velocità

I passaggi seguenti illustrano come usare il middleware di limitazione della frequenza in un'app ASP.NET Core:

  1. Configurare i servizi di limitazione della frequenza.

Nel file Program.cs configurare i servizi di limitazione della frequenza aggiungendo i criteri di limitazione della velocità appropriati. I criteri possono essere definiti come criteri globali o denominati. L'esempio seguente consente 10 richieste al minuto per utente (identità) o a livello globale:

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)
            }));
});

I criteri denominati devono essere applicati in modo esplicito alle pagine o agli endpoint. L'esempio seguente aggiunge un criterio di limite di finestra fisso denominato "fixed" che verrà aggiunto a un endpoint in un secondo momento:

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();

Il limite globale si applica automaticamente a tutti gli endpoint quando viene configurato tramite opzioni. GlobalLimiter.

  1. Abilitare il middleware di limitazione della frequenza

    Nel file Program.cs abilitare il middleware di limitazione della frequenza chiamando UseRateLimiter:

app.UseRouting();

app.UseRateLimiter();

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

app.Run();

Applicare criteri di limitazione della frequenza agli endpoint o alle pagine

Applicare la limitazione della frequenza agli endpoint WEBAPI

Applicare un criterio denominato all'endpoint o al gruppo, ad esempio:


app.MapGet("/api/resource", () => "This endpoint is rate limited")
   .RequireRateLimiting("fixed"); // Apply specific policy to an endpoint

Applicare la limitazione della frequenza ai controller MVC

Applicare i criteri di limitazione della frequenza configurati a endpoint specifici o a livello globale. Ad esempio, per applicare i criteri "fissi" a tutti gli endpoint controller:

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

Applicare la limitazione della velocità alle app lato server Blazor

Per impostare la limitazione della frequenza per tutti i componenti Razor instradabili dell'app, specificare RequireRateLimiting con il nome dei criteri di limitazione della frequenza nella chiamata MapRazorComponents nel file Program. Nell'esempio seguente viene applicato il criterio di limitazione della frequenza denominato "policy" :

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode()
    .RequireRateLimiting("policy");

Per impostare un criterio per un singolo componente Razor instradabile o una cartella di componenti tramite un file di _Imports.razor, l'attributo [EnableRateLimiting] viene applicato con il nome del criterio. Nell'esempio seguente viene applicato il criterio di limitazione della frequenza denominato "override". Il criterio sostituisce tutti i criteri attualmente applicati all'endpoint. Il limitatore globale viene ancora eseguito sull'endpoint con questo attributo applicato.

@page "/counter"
@using Microsoft.AspNetCore.RateLimiting
@attribute [EnableRateLimiting("override")]

<h1>Counter</h1>

L'attributo [EnableRateLimiting] viene applicato solo a un componente instradabile o a una cartella di componenti tramite un file di _Imports.razor se RequireRateLimitingnon vienechiamato su MapRazorComponents.

L'attributo [DisableRateLimiting] viene usato per disabilitare la limitazione della frequenza per un componente instradabile o una cartella di componenti tramite un file _Imports.razor.

Algoritmi di limitazione della frequenza

La RateLimiterOptionsExtensions classe fornisce i metodi di estensione seguenti per la limitazione della frequenza:

I limiti fissi, scorrevoli e token limitano tutto il numero massimo di richieste in un periodo di tempo. Il limite di concorrenza limita solo il numero di richieste simultanee e non limita il numero di richieste in un periodo di tempo. Il costo di un endpoint deve essere considerato quando si seleziona un limiter. Il costo di un endpoint include le risorse usate, ad esempio tempo, accesso ai dati, CPU e I/O.

Limite di finestra fisso

Il AddFixedWindowLimiter metodo usa un intervallo di tempo fisso per limitare le richieste. Quando scade l'intervallo di tempo, viene avviato un nuovo intervallo di tempo e viene reimpostato il limite di richieste.

Osservare il codice seguente:

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();

Il codice precedente:

  • Chiama AddRateLimiter per aggiungere un servizio di limitazione della frequenza alla collezione di servizi.
  • Chiamate AddFixedWindowLimiter per creare un limitatore di finestra fisso con un nome di politica "fixed" e imposta:
  • PermitLimit fino a 4 e l'ora Window fino a 12. È consentito un massimo di 4 richieste per ogni finestra di 12 secondi.
  • Da QueueProcessingOrder a OldestFirst.
  • QueueLimit su 2 (impostalo a 0 per disabilitare il meccanismo di accodamento).
  • Chiama UseRateLimiter per abilitare la limitazione della frequenza.

Le app devono usare Configuration per impostare le opzioni limiter. Il codice seguente aggiorna il codice precedente usando MyRateLimitOptions per la configurazione:

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 essere chiamato dopo UseRouting quando si utilizzano le API specifiche per il controllo della frequenza dell'endpoint. Ad esempio, se viene usato l'attributo [EnableRateLimiting] , UseRateLimiter deve essere chiamato dopo UseRouting. Quando si chiamano solo i limiter globali, è possibile chiamare UseRateLimiter prima di UseRouting.

Limitatore per finestra scorrevole

Algoritmo finestra scorrevole:

  • È simile al limite di finestra fisso, ma aggiunge segmenti per finestra. La finestra scorre di un segmento a ogni intervallo. L'intervallo di segmento è (intervallo di tempo)/(segmenti per finestra).
  • Limita le richieste di una finestra alle permitLimit richieste.
  • Ogni intervallo di tempo è diviso in n segmenti per finestra.
  • Le richieste prelevate da una finestra indietro nel segmento di tempo scaduto (n segmenti prima del segmento corrente) vengono aggiunte al segmento corrente. Si fa riferimento al segmento di tempo più scaduto, una finestra indietro, come segmento scaduto.

Si consideri la tabella seguente che mostra un limite di finestra scorrevole con una finestra di 30 secondi, tre segmenti per finestra e un limite di 100 richieste:

  • La riga superiore e la prima colonna mostrano il segmento di tempo.
  • La seconda riga mostra le richieste rimanenti disponibili. Le richieste rimanenti vengono calcolate come richieste disponibili meno le richieste elaborate più le richieste riciclate.
  • Le richieste in ogni istante si muovono lungo la linea diagonale blu.
  • Dall'ora 30 in poi, la richiesta ottenuta dal segmento di tempo scaduto viene aggiunta nuovamente al limite della richiesta, come illustrato nelle righe rosse.

Tabella che mostra le richieste, i limiti e gli slot riciclati

La tabella seguente mostra i dati nel grafico precedente in un formato diverso. La colonna Available (Disponibile) mostra le richieste disponibili dal segmento precedente (Il trasposto dalla riga precedente). La prima riga mostra 100 richieste disponibili perché non è presente alcun segmento precedente.

Tempo Disponibile Preso Riciclato da materiali scaduti Riportare
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

Il codice seguente usa il limite di frequenza delle finestre scorrevoli:

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();

Limitatore a secchio di gettoni

Il limite del bucket del token è simile al limite della finestra scorrevole, ma invece di aggiungere di nuovo le richieste effettuate dal segmento scaduto, viene aggiunto un numero fisso di token ogni periodo di rifornimento. I token aggiunti a ogni segmento non possono aumentare i token disponibili a un numero superiore al limite del bucket del token. La tabella seguente illustra un limite di bucket di token con un limite di 100 token e un periodo di rifornimento di 10 secondi.

Tempo Disponibile Preso Aggiunto Riportare
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

Il codice seguente usa il limite del bucket del 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 è impostato su true, un timer interno ricostituisce i token ogni ReplenishmentPeriod; se impostato su false, l'app deve chiamare TryReplenish sul limite.

Limite di concorrenza

Il limite di concorrenza limita il numero di richieste simultanee. Ogni richiesta riduce il limite di concorrenza di uno. Al termine di una richiesta, il limite viene aumentato di uno. A differenza degli altri limiter di richieste che limitano il numero totale di richieste per un periodo specificato, il limite di concorrenza limita solo il numero di richieste simultanee e non limita il numero di richieste in un periodo di tempo.

Il codice seguente usa il limite di concorrenza:

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();

Partizioni di limitazione della velocità di trasmissione

Le partizioni di limitazione della frequenza dividono il traffico in contenitori separati, ciascuno ottiene i suoi contatori di limiti di frequenza. Ciò consente un controllo più granulare di un singolo contatore globale. La partizione "bucket" è definita da chiavi diverse(ad esempio ID utente, indirizzo IP o chiave API).

Vantaggi del partizionamento

  • equità: un utente non può utilizzare l'intero limite di frequenza per tutti
  • granularità: limiti diversi per utenti/risorse diversi
  • sicurezza: migliore protezione da abusi mirati
  • Servizio Stratificato: Supporto ai livelli di servizio con limiti diversi

La limitazione della velocità partizionata consente di controllare con granularità fine sul modo in cui si gestisce il traffico DELL'API garantendo al tempo stesso un'allocazione equa delle risorse.

Per indirizzo 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)
        }));

Per utente 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)
        }));

Tramite chiave di accesso 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)
            }),
    };
});

Per percorso di destinazione

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)
        });
});

Creare limiti concatenati

L'API CreateChained consente di passare più PartitionedRateLimiter che vengono combinati in un unico PartitionedRateLimiter oggetto. Il limitatore combinato esegue tutti i limitatori di input in sequenza.

Il codice seguente 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();

Per altre informazioni, vedere il codice sorgente CreateChained

Scegliere cosa accade quando una richiesta è limitata nella frequenza

Per casi semplici, è sufficiente impostare il codice di stato:

builder.Services.AddRateLimiter(options =>
{
    // Set a custom status code for rejections
    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;

    // Rate limiter configuration...
});

L'approccio più comune consiste nel registrare un callback OnRejected durante la configurazione della limitazione della frequenza:

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);
    };
});

Un'altra opzione consiste nel accodare la richiesta:

Accodamento richieste

Con l'accodamento abilitato, quando una richiesta supera il limite di frequenza, viene inserita in una coda in cui la richiesta attende fino a quando un permesso non diventa disponibile o fino a quando non si verifica un timeout. Le richieste vengono elaborate in base a un ordine di coda configurabile.

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
    });
});

Attributi EnableRateLimiting e DisableRateLimiting

Gli [EnableRateLimiting] attributi e [DisableRateLimiting] possono essere applicati a un controller, a un metodo di azione o Razor a una pagina. Per Razor Pages, l'attributo deve essere applicato alla Razor pagina e non ai gestori di pagina. Ad esempio, [EnableRateLimiting] non può essere applicato a OnGet, OnPosto a qualsiasi altro gestore di pagine.

L'attributo [DisableRateLimiting]disabilita la limitazione della frequenza al controller, al metodo di azione o Razor alla pagina indipendentemente dai limiti di frequenza denominati o dai limiter globali applicati. Si consideri ad esempio il codice seguente che chiama RequireRateLimiting per applicare la limitazione della fixedPolicy frequenza a tutti gli endpoint controller:

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();

Nel seguente codice, [DisableRateLimiting] disabilita la limitazione della frequenza e l'override [EnableRateLimiting("fixed")] applicato a Home2Controller e app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy) chiamati in 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 });
    }
}

Nel codice precedente, [EnableRateLimiting("sliding")] non viene applicato al metodo di azione perché è stato chiamato Privacy.

Si consideri il seguente codice che non chiama RequireRateLimiting su MapRazorPages o 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();

Prendere in considerazione il controller seguente:

[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 });
    }
}

Nel controller precedente:

  • Il limitatore di frequenza delle politiche "fixed" viene applicato a tutti i metodi di azione che non dispongono degli attributi EnableRateLimiting e DisableRateLimiting.
  • Il "sliding" limitatore di velocità delle politiche viene applicato all'azione Privacy.
  • La limitazione della frequenza è disabilitata nel NoLimit metodo di azione.

Metriche di limitazione della frequenza

Il middleware di limitazione della frequenza fornisce metriche predefinite e funzionalità di monitoraggio per capire come i limiti di frequenza influiscono sulle prestazioni dell'app e sull'esperienza dell'utente. Per un elenco delle metriche, vedere Microsoft.AspNetCore.RateLimiting.

Test degli endpoint con limitatore di velocità

Prima di distribuire un'app usando la limitazione della velocità per la produzione, sottoporre l'app a stress test per convalidare i limitatori di velocità e le opzioni utilizzate. Ad esempio, crea uno script JMeter con uno strumento come BlazeMeter o Apache JMeter HTTP(S) Test Script Recorder e carica lo script su Azure Test di Carico.

La creazione di partizioni con input utente rende l'app vulnerabile agli attacchi Denial of Service (DoS). Ad esempio, la creazione di partizioni negli indirizzi IP client rende l'app vulnerabile agli attacchi Denial of Service che usano lo spoofing degli indirizzi IP di origine. Per ulteriori informazioni, vedere BCP 38 RFC 2827 Network Ingress Filtering: Defeating Denial of Service Attacks that employ IP Source Address Spoofing.

Risorse aggiuntive