Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
Door Arvin Kahbazi, Maarten Balliauw, en Rick Anderson
De Microsoft.AspNetCore.RateLimiting middleware biedt snelheidsbeperkende middleware. Apps configureren beleidsregels voor snelheidsbeperking en voegen vervolgens het beleid toe aan eindpunten. Apps die frequentielimiet gebruiken, moeten zorgvuldig worden getest en gecontroleerd voordat ze worden geïmplementeerd. Zie Testeindpunten met snelheidsbeperking in dit artikel voor meer informatie.
Zie Rate limiting middlewarevoor een inleiding tot snelheidsbeperking.
Waarom frequentiebeperking gebruiken
Snelheidsbeperking kan worden gebruikt voor het beheren van de stroom van binnenkomende aanvragen naar een app. Belangrijke redenen voor het implementeren van snelheidsbeperking:
- Misbruik voorkomen: snelheidsbeperking helpt een app te beschermen tegen misbruik door het aantal aanvragen te beperken dat een gebruiker of client in een bepaalde periode kan indienen. Deze beveiliging is met name belangrijk voor openbare API's.
- Fair Usage garanderen: Door limieten in te stellen die voorkomen dat gebruikers het systeem in beslag kunnen maken, zorgt u ervoor dat alle gebruikers eerlijke toegang hebben tot resources.
- Resources beveiligen: snelheidsbeperking helpt te voorkomen dat de server overbelast raakt door het aantal aanvragen te beheren dat kan worden verwerkt. Het beschermt de backendresources tegen overbelasting.
- Beveiliging verbeteren: Het kan het risico op Denial of Service-aanvallen (DoS) beperken door de snelheid te beperken waarmee aanvragen worden verwerkt. Het maakt het moeilijker voor aanvallers om een systeem te overspoelen.
- Prestaties verbeteren: Door de snelheid van binnenkomende aanvragen te beheren, kunt u optimale prestaties en reactiesnelheid van een app behouden, waardoor een betere gebruikerservaring wordt gegarandeerd.
- Cost Management-: Voor diensten waarvoor de kosten worden berekend op basis van gebruik, kan het beperken van het aantal verzoeken helpen bij het beheren en voorspellen van uitgaven door het aantal verwerkte aanvragen te controleren.
Het implementeren van snelheidsbeperking in een ASP.NET Core-app kan helpen bij het handhaven van stabiliteit, beveiliging en prestaties. Het resultaat is een betrouwbare en efficiënte service voor alle gebruikers.
DDoS-aanvallen voorkomen
Hoewel snelheidsbeperking kan helpen het risico van Denial of Service-aanvallen (DoS) te beperken door de snelheid te beperken waarmee aanvragen worden verwerkt, is het geen uitgebreide oplossing voor DDoS-aanvallen (Distributed Denial of Service). DDoS-aanvallen hebben betrekking op meerdere systemen die een app overweldigen met een stroom aanvragen, waardoor het moeilijk is om alleen met snelheidsbeperking om te gaan.
Voor robuuste DDoS-beveiliging kunt u overwegen een commerciële DDoS-beveiligingsservice te gebruiken. Deze services bieden geavanceerde functies zoals:
- Verkeersanalyse: continue bewaking en analyse van binnenkomend verkeer om DDoS-aanvallen in realtime te detecteren en te beperken.
- schaalbaarheid: de mogelijkheid om grootschalige aanvallen te verwerken door verkeer over meerdere servers en datacenters te distribueren.
- Geautomatiseerde beperking: geautomatiseerde responsmechanismen om snel schadelijk verkeer te blokkeren zonder handmatige tussenkomst.
- Globaal netwerk: een wereldwijd netwerk van servers om aanvallen dichter bij de bron te absorberen en te beperken.
- Constante updates: commerciële services houden hun beveiligingsmechanismen continu bij en werken ze bij om zich aan te passen aan nieuwe en veranderende bedreigingen.
Wanneer u een cloudhostingservice gebruikt, is DDoS-beveiliging meestal beschikbaar als onderdeel van de hostingoplossing, zoals Azure Web Application Firewall, AWS Shield of Google Cloud Armor. Toegewezen beveiligingen zijn beschikbaar als Web Application Firewalls (WAF) of als onderdeel van een CDN-oplossing, zoals Cloudflare of Akamai Kona Site Defender
Het implementeren van een commerciële DDoS-beveiligingsservice in combinatie met snelheidsbeperking kan een uitgebreide verdedigingsstrategie bieden, waardoor de stabiliteit, beveiliging en prestaties van een app worden gegarandeerd.
Snelheidsbeperkings-middleware gebruiken
De volgende stappen laten zien hoe u de snelheidsbeperkings-middleware gebruikt in een ASP.NET Core-app:
- Configureer snelheidsbeperkingsservices.
Configureer in het bestand Program.cs de services voor snelheidsbeperking door het juiste beleid voor frequentiebeperking toe te voegen. Definieer beleid als globaal beleid of benoemd beleid. In het volgende voorbeeld worden 10 aanvragen per minuut per gebruiker (identiteit) of globaal toegestaan:
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)
}));
});
Benoemde beleidsregels moeten expliciet worden toegepast op de pagina's of eindpunten. In het volgende voorbeeld wordt een vast beleid voor vensterbegrenzers toegevoegd met de naam "fixed" die u later aan een eindpunt toevoegt:
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();
De globale begrenzer is automatisch van toepassing op alle eindpunten wanneer u deze configureert via opties. GlobalLimiter.
Middleware voor snelheidsbeperking inschakelen
Schakel in het bestand
Program.csde snelheidsbeperkings-middleware in door UseRateLimiter-aan te roepen:
app.UseRouting();
app.UseRateLimiter();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run();
Beleid voor snelheidsbeperking toepassen op eindpunten of pagina's
Frequentiebeperking toepassen op web-API-eindpunten
Pas een benoemd beleid toe op het eindpunt of de groep, bijvoorbeeld:
app.MapGet("/api/resource", () => "This endpoint is rate limited")
.RequireRateLimiting("fixed"); // Apply specific policy to an endpoint
Frequentiebeperking toepassen op MVC-controllers
Pas het geconfigureerde frequentiebeperkingsbeleid toe op specifieke eindpunten of globaal. Als u bijvoorbeeld het 'vaste' beleid wilt toepassen op alle controllereindpunten:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers().RequireRateLimiting("fixed");
});
Frequentiebeperking toepassen op Blazor-apps aan de serverzijde
Als u frequentiebeperking wilt instellen voor alle routeerbare Razor onderdelen van de app, geeft u RequireRateLimiting op met de beleidsnaam voor frequentiebeperking voor de MapRazorComponents aanroep in het Program-bestand. In het volgende voorbeeld wordt het beleid voor frequentiebeperking met de naam 'policy' toegepast:
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.RequireRateLimiting("policy");
Als u een beleid wilt instellen voor één routeerbaar Razor onderdeel of een map met onderdelen via een importbestand (_Imports.razor), past u het [EnableRateLimiting] kenmerk toe met de beleidsnaam. In het volgende voorbeeld wordt het beleid voor frequentiebeperking met de naam 'override' toegepast. Het beleid vervangt alle beleidsregels die momenteel op het eindpunt worden toegepast. De globale begrenzer wordt nog steeds uitgevoerd op het eindpunt waarop dit kenmerk is toegepast.
@page "/counter"
@using Microsoft.AspNetCore.RateLimiting
@attribute [EnableRateLimiting("override")]
<h1>Counter</h1>
Pas het kenmerk [EnableRateLimiting] alleen toe aan een routeerbare component of een map met componenten via een imports-bestand als RequireRateLimitingniet wordt aangeroepen op MapRazorComponents.
Gebruik het [DisableRateLimiting] kenmerk om snelheidsbeperking uit te schakelen voor een routeerbaar onderdeel of een map met onderdelen via een importbestand.
Algoritmen voor frequentielimieten
De RateLimiterOptionsExtensions-klasse biedt de volgende uitbreidingsmethoden voor snelheidsbeperking:
De vaste, schuif- en tokenbegrenzers beperken allemaal het maximum aantal aanvragen in een bepaalde periode. De gelijktijdigheidsbeperking beperkt alleen het aantal gelijktijdige aanvragen en beperkt het aantal aanvragen in een bepaalde periode niet. Houd rekening met de kosten van een eindpunt wanneer u een begrenzer selecteert. De kosten van een eindpunt omvatten de gebruikte resources, zoals tijd, gegevenstoegang, CPU en I/O.
Vaste vensterbegrenzer
De methode AddFixedWindowLimiter gebruikt een vast tijdvenster om aanvragen te beperken. Wanneer het tijdvenster verloopt, wordt een nieuw tijdvenster gestart en wordt de aanvraaglimiet opnieuw ingesteld.
Houd rekening met de volgende code:
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();
De voorgaande code:
- Roept AddRateLimiter aan om een snelheidsbeperkingsservice toe te voegen aan de serviceverzameling.
- Roept
AddFixedWindowLimiteraan om een vaste vensterbegrenzer te maken met een beleidsnaam van"fixed"en sets: - PermitLimit tot 4 en de tijd Window tot 12. Er zijn maximaal 4 aanvragen per elk venster van 12 seconden toegestaan.
- QueueProcessingOrder tot OldestFirst.
- QueueLimit op 2 (stel dit in op 0 om het mechanisme voor wachtrijen uit te schakelen).
- Roept UseRateLimiter- aan om snelheidsbeperking in te schakelen.
Apps moeten Configuration gebruiken om opties voor limieten in te stellen. Met de volgende code wordt de voorgaande code bijgewerkt met behulp van MyRateLimitOptions voor configuratie:
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 moet worden aangeroepen na UseRouting wanneer eindpuntspecifieke API's voor frequentiebeperking worden gebruikt. Als het kenmerk [EnableRateLimiting] bijvoorbeeld wordt gebruikt, moet UseRateLimiter worden aangeroepen na UseRouting. Wanneer u alleen globale begrenzers aanroept, kan UseRateLimiter worden aangeroepen voordat UseRouting.
Schuifvensterbegrenzer
Een algoritme voor een schuifvenster:
- Is vergelijkbaar met de vaste vensterbegrenzer, maar voegt segmenten per venster toe. Het venster schuift één segment per segmentinterval. Het segmentinterval is (tijdvenster)/(segmenten per venster).
- Hiermee beperkt u het aantal aanvragen voor een venster tot
permitLimit. - Elke tijdvenster wordt verdeeld in
nsegmenten per venster. - Aanvragen uit het verlopen tijdsegment één venster terug (
nsegment vóór het huidige segment) worden toegevoegd aan het huidige segment. We noemen het meest vervallen tijdsegment van één venster terug het verlopen segment.
Bekijk de volgende tabel met een schuifvensterbegrenzer met een venster van 30 seconden, drie segmenten per venster en een limiet van 100 aanvragen:
- In de bovenste rij en eerste kolom wordt het tijdsegment weergegeven.
- In de tweede rij ziet u de resterende aanvragen die beschikbaar zijn. De resterende aanvragen worden berekend als de beschikbare aanvragen minus de verwerkte aanvragen plus de gerecyclede aanvragen.
- Aanvragen bewegen elke keer langs de diagonale blauwe lijn.
- Vanaf tijd 30 wordt de aanvraag uit het verlopen tijdsegment weer toegevoegd aan de aanvraaglimiet, zoals wordt weergegeven in de rode regels.
In de volgende tabel ziet u de gegevens in de vorige grafiek in een andere indeling. In de Beschikbaar kolom worden de aanvragen weergegeven die afkomstig zijn uit het vorige segment (het Overdragen vanuit de vorige rij). In de eerste rij ziet u 100 beschikbare aanvragen omdat er geen vorig segment is.
| Tijd | Beschikbaar | Gepakt | Gerecycled van verlopen materialen | Overboeken |
|---|---|---|---|---|
| 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 |
De volgende code maakt gebruik van de frequentielimiet voor schuifvensters:
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();
Token bucket-begrenzer
De tokenbucketbegrenzer is vergelijkbaar met de schuifvensterbegrenzer, maar in plaats van de aanvragen uit het verlopen segment toe te voegen, wordt elke aanvullingsperiode een vast aantal tokens toegevoegd. De tokens die aan elk segment zijn toegevoegd, kunnen de beschikbare tokens niet verhogen tot een getal dat hoger is dan de limiet voor de tokenbucket. In de volgende tabel ziet u een tokenbucketbegrenzer met een limiet van 100 tokens en een aanvullingsperiode van 10 seconden.
| Tijd | Beschikbaar | Gepakt | Toegevoegd | Overboeken |
|---|---|---|---|---|
| 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 |
De volgende code maakt gebruik van de bucketbegrenzer voor tokens:
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();
Wanneer AutoReplenishment is ingesteld op true, vult een interne timer de tokens aan met een interval van ReplenishmentPeriod; als deze is ingesteld op false, moet de app TryReplenish aanroepen op de limiter.
Beperkingsmechanisme voor gelijktijdigheid
De gelijktijdigheidsbeperking beperkt het aantal gelijktijdige aanvragen. Elke aanvraag vermindert de gelijktijdigheidslimiet met één. Wanneer een aanvraag is voltooid, wordt de limiet met één verhoogd. In tegenstelling tot de andere limieten voor aanvragen die het totale aantal aanvragen voor een opgegeven periode beperken, beperkt de limiet voor gelijktijdigheid alleen het aantal gelijktijdige aanvragen en wordt het aantal aanvragen in een bepaalde periode niet beperkt.
De volgende code maakt gebruik van de gelijktijdigheidslimiet:
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();
Frequentiebeperking van partities
Frequentiebeperkingspartities verdelen het verkeer in afzonderlijke buckets die elk hun eigen frequentielimiettellers krijgen. Deze benadering biedt gedetailleerdere controle dan één globaal teller. Verschillende sleutels, zoals gebruikers-id, IP-adres of API-sleutel, definiëren de partitiebuckets.
Voordelen van partitioneren
- Billijkheid: één gebruiker kan de volledige frequentielimiet voor iedereen niet gebruiken.
- Granulariteit: Verschillende limieten voor verschillende gebruikers en resources.
- Beveiliging: betere bescherming tegen gerichte misbruik.
- Gelaagde service: ondersteuning voor servicelagen met verschillende limieten.
Gepartitioneerde snelheidsbeperking biedt u nauwkeurige controle over hoe u API-verkeer beheert en zorgt voor eerlijke resourcetoewijzing.
Op basis van IP-adres
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 gebruikersidentiteit
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
RateLimitPartition.GetFixedWindowLimiter(
partitionKey: httpContext.User.Identity?.Name ?? "anonymous",
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 100,
Window = TimeSpan.FromMinutes(1)
}));
Op API-sleutelbasis
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)
}),
};
});
Op basis van eindpuntpad
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)
});
});
Gekoppelde begrenzers maken
De CreateChained API accepteert meerdere PartitionedRateLimiter exemplaren en combineert deze in één PartitionedRateLimiter. Met de gecombineerde begrenzer worden alle invoerbegrenzers op volgorde uitgevoerd. Omdat het resultaat is PartitionedRateLimiter toegewezen aan GlobalLimiter, is de keten van toepassing op elk eindpunt. Als u in plaats daarvan ketenbegrenzers voor een specifiek eindpunt wilt gebruiken, gebruikt u een benoemd beleid, zoals wordt weergegeven in ketenbegrenzers in een benoemd beleid.
De volgende code maakt gebruik van 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();
Zie de broncode CreateChained voor meer informatie.
Ketenbegrenzers in een benoemd beleid
CreateChained schakelt globale limiters in die voor elk eindpunt gelden. Als u meerdere typen grenzers wilt combineren en deze wilt beperken tot specifieke eindpunten, koppelt u de limieten binnen een benoemd beleid met CreateChained. Deze overbelasting retourneert één RateLimiter die elke begrenzer op volgorde uitvoert. Dit is het retourtype dat de partitiefactory van een benoemd beleid vereist.
Het volgende "combined"-beleid schakelt een token bucket-limiter en een concurrency-limiter aaneen met RateLimiter.CreateChained, en past het beleid vervolgens toe op één endpoint met 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");
Een verzoek moet van elke limiter in de keten een lease verkrijgen om verder te gaan, en de limiters worden uitgevoerd in de volgorde die aan RateLimiter.CreateChained is doorgegeven. Als een begrenzer de aanvraag afwijst, wordt de aanvraag geweigerd en worden de leases die al zijn verkregen van eerdere limieten in de keten, in omgekeerde volgorde verwijderd.
Houd rekening met het volgende bij het koppelen van begrenzers in een benoemd beleid:
- Door een lease vrij te geven, wordt de toestemming voor een limiet voor gelijktijdigheid teruggegeven. De op tijd gebaseerde limiters (token bucket, fixed window en sliding window) geven geen al verkregen toestemming terug als een latere limiter in de keten het verzoek afwijst, dus houd hier rekening mee bij het bepalen van de volgorde van de limiters in de keten.
- Het rechtstreeks construeren van een
TokenBucketRateLimitermetAutoReplenishmentingesteld optruegeeft elke limiterinstantie zijn eigen timer. DeAddTokenBucketLimiter- enRateLimitPartition.GetTokenBucketLimiter-helpers stellen in plaats daarvanAutoReplenishmentin opfalseen vullen al hun limiters opnieuw aan vanuit één gedeelde timer. -
RateLimiter.CreateChainedgeeft de eraan doorgegeven begrenzers niet vrij. In het vorige voorbeeld slaat de partitie de gekoppelde begrenzer in de cache op en beheert het framework de levensduur. Als u kettingbegrenzers buiten een partitie maakt, moet u de binnenbegrenzers verwijderen wanneer ze niet meer in gebruik zijn. - Geef de voorkeur aan de globale
PartitionedRateLimiter.CreateChainedbenadering wanneer de keten moet worden toegepast op elk eindpunt. Gebruik een benoemd beleid metRateLimiter.CreateChainedalleen wanneer de keten moet worden beperkt tot specifieke eindpunten.
Kiezen wat er gebeurt wanneer een aanvraag in snelheid wordt beperkt
Voor eenvoudige gevallen kunt u gewoon de statuscode instellen:
builder.Services.AddRateLimiter(options =>
{
// Set a custom status code for rejections
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
// Rate limiter configuration...
});
De meest voorkomende benadering is het registreren van een OnRejected callback bij het configureren van frequentielimieten:
builder.Services.AddRateLimiter(options =>
{
// Rate limiter configuration...
options.OnRejected = async (context, cancellationToken) =>
{
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
{
context.HttpContext.Response.Headers.RetryAfter =
((long)Math.Ceiling(retryAfter.TotalSeconds)).ToString(NumberFormatInfo.InvariantInfo);
}
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);
};
});
Het voorbeeld vereist using System.Globalization; en using System.Threading.RateLimiting;.
Note
FixedWindowRateLimiter rapporteert MetadataName.RetryAfter op basis van de volgende grens met vaste vensters. Apps die deze metagegevens naar de Retry-After-antwoordheader in OnRejected kopiëren, retourneren automatisch een nauwkeurig interval voor opnieuw proberen. Begrenzers die geen schatting kunnen maken wanneer permits beschikbaar zijn, zoals ConcurrencyLimiter, bieden RetryAfter metagegevens.
Een andere optie is om de aanvraag in de wachtrij te plaatsen:
Wachtrijen aanvragen
Wanneer u wachtrijen inschakelt en een aanvraag de frequentielimiet overschrijdt, plaatst het systeem deze in een wachtrij. Het verzoek wacht in de wachtrij totdat toestemming beschikbaar komt of er een time-out optreedt. Het systeem verwerkt aanvragen volgens een configureerbare wachtrijvolgorde.
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
});
});
kenmerken EnableRateLimiting en DisableRateLimiting
Pas de [EnableRateLimiting] en [DisableRateLimiting] kenmerken toe op een controller, actiemethode of Razor Pagina. Pas voor Razor Pages het attribuut toe op de Razor-pagina en niet op de paginahandlers. U kunt bijvoorbeeld [EnableRateLimiting] niet toepassen op OnGet, OnPost of een andere paginahandler.
Het [DisableRateLimiting] kenmerk schakelt snelheidsbeperking voor de controller, actiemethode of Razor Pagina uit, ongeacht benoemde frequentielimieten of globale limieten die zijn toegepast. Denk bijvoorbeeld aan de volgende code die RequireRateLimiting aanroept om de fixedPolicy frequentielimiet toe te passen op alle controllereindpunten:
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();
In de volgende code schakelt [DisableRateLimiting] snelheidsbeperking uit en negeert de [EnableRateLimiting("fixed")] die wordt toegepast op de Home2Controller en app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy) die in Program.csworden aangeroepen.
[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 });
}
}
In de voorgaande code wordt de [EnableRateLimiting("sliding")] niet toegepast op de actiemethode omdat Privacy is aangeroepen.
Houd rekening met de volgende code die geen RequireRateLimiting aanroept op MapRazorPages of 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();
Houd rekening met de volgende controller:
[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 });
}
}
Bij de eerdere controller:
- De
"fixed"beleidsfrequentielimiet wordt toegepast op alle actiemethoden die geenEnableRateLimitingenDisableRateLimitingkenmerken hebben. - De
"sliding"beleidssnelheidslimiet wordt toegepast op de actiePrivacy. - Snelheidsbeperking is uitgeschakeld voor de actiemethode
NoLimit.
Snelheidslimietmetriek
De middleware voor snelheidsbeperking biedt ingebouwde metrische gegevens en bewakingsmogelijkheden om inzicht te krijgen in de invloed van frequentielimieten op de prestaties en gebruikerservaring van apps. Zie voor een lijst met metrische gegevens Microsoft.AspNetCore.RateLimiting.
Eindpunten testen met snelheidsbeperking
Voordat u een app die gebruikmaakt van ratelimieten naar productie uitrolt, onderwerpt u de app aan een stresstest om de gebruikte ratelimiters en opties te valideren. Maak bijvoorbeeld een JMeter-script met behulp van een hulpprogramma zoals BlazeMeter of Apache JMeter HTTP(S) TestScript Recorder en laad het script naar Azure Load Testing.
Als u partities maakt met behulp van gebruikersinvoer, wordt uw app kwetsbaar voor DoS-aanvallen ( Denial of Service ). Als u bijvoorbeeld partities maakt met behulp van IP-adressen van clients, wordt uw app kwetsbaar voor Denial of Service-aanvallen die ip-bronadresvervalsing gebruiken. Zie voor meer informatie BCP 38 RFC 2827 Netwerktoegangsfiltering: Het tegengaan van Denial of Service-aanvallen die IP-adresvervalsing gebruiken.
Verwante onderwerpen
- Rate limiting middleware van Maarten Balliauw biedt een uitstekende inleiding en overzicht van snelheidsbeperking.
- frequentielimiet voor een HTTP-handler in .NET