Delen via


Snelheidsbeperking middleware in ASP.NET Core

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. Dit is met name belangrijk voor openbare API's.
  • Zorgen voor eerlijk gebruik: door limieten in te stellen, hebben alle gebruikers eerlijke toegang tot resources, waardoor gebruikers het systeem niet kunnen beperken.
  • Bescherming van bronnen: Ratelimiting helpt te voorkomen dat de server overbelast raakt door het aantal aanvragen te beheren dat kan worden verwerkt, waardoor de backend-resources niet worden overspoeld.
  • Verbeteren van beveiliging: Het kan het risico op Denial of Service-aanvallen (DoS) beperken door de snelheid te beperken waarmee aanvragen worden verwerkt, waardoor het moeilijker wordt voor aanvallers om een systeem te overspoelen.
  • Prestaties verbeteren: door de snelheid van binnenkomende aanvragen te beheren, kunnen optimale prestaties en reactiesnelheid van een app worden gehandhaafd, 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, waardoor een betrouwbare en efficiënte service voor alle gebruikers wordt gegarandeerd.

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:

  • Traffic Analysis: 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.
  • Automatische beperking: geautomatiseerde responsmechanismen om snel schadelijk verkeer te blokkeren zonder handmatige tussenkomst.
  • Global Network: een wereldwijd netwerk van servers om aanvallen dichter bij de bron te absorberen en te beperken.
  • Constante updates: commerciële diensten beheren en actualiseren continu hun beveiligingsmechanismen 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.

Middleware voor snelheidsbeperking gebruiken

De volgende stappen laten zien hoe u de snelheidsbeperkings-middleware gebruikt in een ASP.NET Core-app:

  1. Configureer snelheidsbeperkingsservices.

Configureer in het bestand Program.cs de services voor snelheidsbeperking door het juiste beleid voor frequentiebeperking toe te voegen. Beleidsregels kunnen worden gedefinieerd als globaal 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)
            }));
});

Benoemd beleid moet expliciet worden toegepast op de pagina's of eindpunten. In het volgende voorbeeld wordt een vast beleid voor vensterlimieten met de naam "fixed" toegevoegd, dat later aan een eindpunt wordt toegevoegd:

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 deze wordt geconfigureerd via opties. GlobalLimiter.

  1. Middleware voor snelheidsbeperking inschakelen

    Schakel in het bestand Program.cs de 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 WebAPI-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 _Imports.razor bestand, wordt het [EnableRateLimiting] kenmerk toegepast 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>

Het [EnableRateLimiting] kenmerk wordt alleen toegepast op een routeerbaar onderdeel of een map met onderdelen via een _Imports.razor bestand als RequireRateLimitingniet aangeroepen op MapRazorComponents.

Het [DisableRateLimiting] kenmerk wordt gebruikt om snelheidsbeperking voor een routeerbaar onderdeel of een map met onderdelen uit te schakelen via een _Imports.razor-bestand.

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. De kosten van een eindpunt moeten worden overwogen bij het selecteren van een begrenzer. De kosten van een eindpunt omvatten de gebruikte resources, bijvoorbeeld 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 AddFixedWindowLimiter aan 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 n segmenten per venster.
  • Aanvragen uit het verlopen tijdsegment één venster terug (n segment 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.

Tabel met verzoeken, limieten en gerecyclede sleuven

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

Beperkingsonderdelen

Frequentiebeperkingspartities verdelen het verkeer in afzonderlijke buckets die elk hun eigen frequentielimiettellers krijgen. Dit maakt een gedetailleerdere controle mogelijk dan één globale teller. De partitie buckets worden gedefinieerd door verschillende sleutels (zoals gebruikers-id, IP-adres of API-sleutel).

Voordelen van partitioneren

  • Fairness: een gebruiker kan niet de volledige ratelimiet voor iedereen gebruiken
  • Granulariteit: verschillende limieten voor verschillende gebruikers/resources
  • Security: Betere bescherming tegen gericht 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)
        }));

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

API-sleutel

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 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

Met de CreateChained-API kunnen meerdere PartitionedRateLimiter worden doorgegeven die in één PartitionedRateLimiterworden gecombineerd. Met de gecombineerde begrenzer worden alle invoerbegrenzers op volgorde uitgevoerd.

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 CreateChained-broncode voor meer informatie

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 methode is het registreren van een onRejected callback bij het configureren van frequentiebeperking:

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

Een andere optie is om de aanvraag in de wachtrij te plaatsen:

Wachtrijen aanvragen

Wanneer wachtrijen zijn ingeschakeld, wordt een aanvraag in een wachtrij geplaatst wanneer een aanvraag de frequentielimiet overschrijdt, waarbij de aanvraag wacht totdat een vergunning beschikbaar is of totdat er een time-out optreedt. Aanvragen worden verwerkt 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

De kenmerken [EnableRateLimiting] en [DisableRateLimiting] kunnen worden toegepast op een controller, actiemethode of Razor-pagina. Voor Razor Pagina's moet het kenmerk worden toegepast op de Razor Pagina en niet op de pagina-handlers. [EnableRateLimiting] kan bijvoorbeeld niet worden toegepast op OnGet, OnPostof een andere pagina-handler.

Het kenmerk [DisableRateLimiting]schakelt snelheidsbeperking voor controller, actiemethode of Razor pagina uit, ongeacht de 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 geen EnableRateLimiting en DisableRateLimiting kenmerken hebben.
  • De "sliding" beleidssnelheidslimiet wordt toegepast op de actie Privacy.
  • Snelheidsbeperking is uitgeschakeld voor de actiemethode NoLimit.

Snelheidslimietmetriek

De rate limiting middleware biedt ingebouwde metrische gegevens en bewaking mogelijkheden om inzicht te krijgen in de snelheidslimieten die van invloed zijn op de prestaties en gebruikerservaring van apps. Zie Microsoft.AspNetCore.RateLimiting voor een lijst met metrische gegevens.

Eindpunten testen met snelheidsbeperking

Voordat u een app implementeert met tariferingsbeperking aan productie, voert u een stresstest uit op de app om de tariferingslimieten en de gebruikte opties te valideren. Maak bijvoorbeeld een JMeter-script met een hulpprogramma zoals BlazeMeter of Apache JMeter HTTP(S) Test Script Recorder en laad het script naar Azure Load Testing.

Als u partities maakt met gebruikersinvoer, is de app kwetsbaar voor DoS-aanvallen (Denial of Service). Als u bijvoorbeeld partities maakt op client-IP-adressen, is de app kwetsbaar voor Denial of Service-aanvallen die gebruikmaken van IP-bronadresvervalsing. Zie voor meer informatie BCP 38 RFC 2827 Netwerktoegangsfiltering: Het tegengaan van Denial of Service-aanvallen die IP-adresvervalsing gebruiken.

Aanvullende informatiebronnen