Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
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. Questa protezione è particolarmente importante per le API pubbliche.
Verifica dell'utilizzo equo: impostando limiti che impediscono agli utenti di monopolizzare il sistema, assicurarsi che tutti gli utenti abbiano accesso equo alle risorse.
Protezione delle risorse: la limitazione della frequenza consente di evitare l'overload del server controllando il numero di richieste che possono essere elaborate. Protegge le risorse back-end dal sovraccarico.
Miglioramento della sicurezza: può ridurre il rischio di attacchi Denial of Service (DoS) limitando la frequenza con cui vengono elaborate le richieste. Rende più difficile per gli utenti malintenzionati inondare un sistema.
Miglioramento delle 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. Il risultato è un servizio affidabile ed efficiente per tutti gli utenti.
Prevenire gli 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 continue del traffico in ingresso per rilevare e mitigare gli attacchi DDoS in tempo reale.
- scalabilità: possibilità di gestire attacchi su larga scala distribuendo il traffico tra più server e data center.
- 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.
Utilizzare middleware per la limitazione della velocità delle richieste
I passaggi seguenti illustrano come usare il middleware di limitazione della frequenza in un'app ASP.NET Core:
- 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. Definire i criteri 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 viene 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.
Abilitare il middleware di limitazione della frequenza
Nel file
Program.csabilitare 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 dell'API Web
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 instradabile Razor o una cartella di componenti tramite un file imports (_Imports.razor), applicare l'[EnableRateLimiting]attributo 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>
Applicare l'attributo[EnableRateLimiting] solo a un componente instradabile o a una cartella di componenti tramite un file di importazione se RequireRateLimitingnon viene chiamato su MapRazorComponents.
Usare l'attributo per disabilitare la [DisableRateLimiting] limitazione della frequenza per un componente instradabile o una cartella di componenti tramite un file di importazione.
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. Prendere in considerazione il costo di un endpoint 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. Alla scadenza dell'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
AddFixedWindowLimiterper 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
permitLimitrichieste. - Ogni intervallo di tempo è diviso in
nsegmenti per finestra. - Le richieste prelevate da una finestra indietro nel segmento di tempo scaduto (
nsegmenti 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.
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();
Limitazione della frequenza delle partizioni
Le partizioni di limitazione della frequenza dividono il traffico in bucket separati che ottengono i propri contatori dei limiti di velocità. Questo approccio offre un controllo più granulare rispetto a un singolo contatore globale. Chiavi diverse, ad esempio ID utente, indirizzo IP o chiave API, definiscono i bucket di partizione.
Vantaggi del partizionamento
- Equità: un utente non può utilizzare l'intero limite di velocità per tutti.
- Granularità: limiti diversi per utenti e risorse diversi.
- Sicurezza: migliore protezione da abusi mirati.
- Servizio a livelli: supporto per i 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.
In base all'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 identità utente
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
RateLimitPartition.GetFixedWindowLimiter(
partitionKey: httpContext.User.Identity?.Name ?? "anonymous",
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 100,
Window = TimeSpan.FromMinutes(1)
}));
Per chiave 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)
}),
};
});
In base al percorso dell'endpoint
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 accetta più PartitionedRateLimiter istanze e le combina in un unico PartitionedRateLimiter. Il limitatore combinato esegue tutti i limitatori di input in sequenza. Poiché il risultato è un PartitionedRateLimiter assegnato a GlobalLimiter, la catena si applica a ogni endpoint. Per concatenare i limitatori per un endpoint specifico, usare invece un criterio denominato, come illustrato in Concatenare i limitatori in un criterio denominato.
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.
Limiter di catena in un criterio denominato
CreateChained catene di limiti globali che si applicano a ogni endpoint. Per combinare più tipi di limitatore e applicarli a endpoint specifici, concatenare i limitatori all'interno di un criterio con nome con CreateChained. Questo sovraccarico restituisce un singolo oggetto RateLimiter che esegue ogni limitatore in sequenza, che è il tipo restituito richiesto dalla factory di partizione di un criterio con nome.
I criteri seguenti "combined" concatenano un limite di bucket di token e un limiter di concorrenza con RateLimiter.CreateChained, quindi applica i criteri a un singolo endpoint con RequireRateLimiting:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRateLimiter(options =>
{
options.AddPolicy("combined", httpContext =>
{
// Partition on the authenticated identity name when available. Each distinct key creates and
// caches its own limiter, so partitioning on unbounded user-controlled
// input can exhaust memory (a DoS risk).
string partitionKey = httpContext.User.Identity?.Name ?? "anonymous";
return RateLimitPartition.Get(partitionKey, _ =>
RateLimiter.CreateChained(
new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions
{
TokenLimit = 100,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 5,
ReplenishmentPeriod = TimeSpan.FromSeconds(10),
TokensPerPeriod = 10,
AutoReplenishment = true
}),
new ConcurrencyLimiter(new ConcurrencyLimiterOptions
{
PermitLimit = 5,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 2
})));
});
});
var app = builder.Build();
app.MapGet("/api/resource", () => "This endpoint uses multiple limiters")
.RequireRateLimiting("combined");
Per procedere, una richiesta deve acquisire una concessione da ogni limitatore della catena e i limitatori vengono eseguiti nell'ordine in cui sono passati a RateLimiter.CreateChained. Se un limiter rifiuta la richiesta, la richiesta viene rifiutata e i lease già acquisiti dai limiter precedenti nella catena vengono eliminati in ordine inverso.
Quando si concatenano i limitatori in un criterio con nome, tenere presente quanto segue:
- Il rilascio di un lease restituisce il permesso a un limitatore di concorrenza. I limitatori basati sul tempo (bucket di token, finestra fissa e finestra scorrevole) non restituiscono un permesso già acquisito quando un limitatore successivo nella catena rifiuta la richiesta, quindi occorre tenerne conto quando si stabilisce l’ordine dei limitatori nella catena.
- La creazione di un oggetto
TokenBucketRateLimiterdirettamente conAutoReplenishmentimpostato sutrueassegna a ciascuna istanza del limitatore il proprio timer. Gli helperAddTokenBucketLimitereRateLimitPartition.GetTokenBucketLimiterinvece impostanoAutoReplenishmentsufalsee reintegrano tutti i loro limitatori da un unico timer condiviso. -
RateLimiter.CreateChainednon rilascia i limitatori che gli vengono passati. Nell'esempio precedente la partizione memorizza nella cache il limite concatenato e il framework ne gestisce la durata. Se si creano limiti concatenati all'esterno di una partizione, eliminare i limiti interni quando non sono più in uso. - Preferire l'approccio globale
PartitionedRateLimiter.CreateChainedquando la catena deve essere applicata a ogni endpoint. Usare un criterio denominato conRateLimiter.CreateChainedsolo quando la catena deve avere come ambito endpoint specifici.
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 OnRejected callback 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 delle richieste
Quando si abilita l'accodamento, se una richiesta supera il limite di frequenza, il sistema lo inserisce in una coda. La richiesta rimane in coda finché non si rende disponibile un permesso o non si verifica un timeout. Il sistema elabora le richieste 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
Applicare gli attributi [EnableRateLimiting] e [DisableRateLimiting] a un controller, a un metodo di azione o a una pagina Razor. Per Razor Pages, applicare l'attributo alla Razor pagina e non ai gestori di pagina. Ad esempio, non è possibile applicare [EnableRateLimiting] a OnGet, OnPosto a qualsiasi altro gestore di pagine.
L'attributo [DisableRateLimiting]disabilita la limitazione della frequenza per il controller, il metodo di azione o Razor la 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 attributiEnableRateLimitingeDisableRateLimiting. - Il
"sliding"limitatore di velocità delle politiche viene applicato all'azionePrivacy. - La limitazione della frequenza è disabilitata nel
NoLimitmetodo di azione.
Metriche di limitazione della frequenza
Il middleware di limitazione della frequenza offre funzionalità predefinite di monitoraggio e metriche per comprendere in che modo i limiti di frequenza influiscono sulle prestazioni delle app e sull'esperienza utente. Per un elenco delle metriche, vedere Microsoft.AspNetCore.RateLimiting.
Test degli endpoint con limitatore di velocità
Prima di distribuire in produzione un'app che usa la limitazione della frequenza, sottoponi l'app a test di carico per convalidare i limitatori di frequenza e le opzioni utilizzate. Ad esempio, creare uno script JMeter usando uno strumento come BlazeMeter o Apache JMeter HTTP(S) Test Script Recorder e caricare lo script in Test di carico di Azure.
Se si creano partizioni usando l'input dell'utente, l'app diventa vulnerabile agli attacchi Denial of Service (DoS). Ad esempio, se si creano partizioni usando indirizzi IP client, l'app diventa vulnerabile agli attacchi Denial of Service che usano spoofing dell'indirizzo 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
- Il middleware di limitazione del tasso di Maarten Balliauw offre un'eccellente introduzione e panoramica su come limitare il tasso.
- Limitare la frequenza di un gestore HTTP in .NET