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