Dela via


Hastighetsbegränsning av mellanprogram i ASP.NET Core

Av Arvin Kahbazi, Maarten Balliauwoch Rick Anderson

Den Microsoft.AspNetCore.RateLimiting-mellanprogrammet tillhandahåller mellanprogram för hastighetsbegränsning. Appar konfigurerar principer för hastighetsbegränsning och kopplar sedan principerna till slutpunkter. Appar som använder hastighetsbegränsning bör noggrant läsas in och granskas innan de distribueras. Mer information finns i Testa slutpunkter med hastighetsbegränsning i den här artikeln.

En introduktion till hastighetsbegränsning finns i Hastighetsbegränsning för mellanprogram.

Varför använda hastighetsbegränsning

Hastighetsbegränsning kan användas för att hantera flödet av inkommande begäranden till en app. Viktiga orsaker till att implementera hastighetsbegränsning:

  • Förhindra missbruk: Hastighetsbegränsning hjälper till att skydda en app från missbruk genom att begränsa antalet begäranden som en användare eller klient kan göra under en viss tidsperiod. Detta är särskilt viktigt för offentliga API:er.
  • Att säkerställa rättvis användning: Genom att ange gränser har alla användare rättvis åtkomst till resurser, vilket hindrar användare från att monopolisera systemet.
  • Skydda resurser: Hastighetsbegränsning hjälper till att förhindra överbelastning av servern genom att kontrollera antalet begäranden som kan bearbetas, vilket skyddar serverdelsresurserna från att överbelastas.
  • Förbättra säkerheten: Det kan minska risken för DoS-attacker (Denial of Service) genom att begränsa den hastighet med vilken begäranden bearbetas, vilket gör det svårare för angripare att översvämma ett system.
  • Förbättra prestanda: Genom att styra antalet inkommande begäranden kan optimal prestanda och svarstider för en app upprätthållas, vilket ger en bättre användarupplevelse.
  • Cost Management-: För tjänster som medför kostnader baserat på användning kan hastighetsbegränsning hjälpa till att hantera och förutsäga utgifter genom att kontrollera mängden begäranden som bearbetas.

Genom att implementera hastighetsbegränsning i en ASP.NET Core-app kan du upprätthålla stabilitet, säkerhet och prestanda, vilket säkerställer en tillförlitlig och effektiv tjänst för alla användare.

Förhindra DDoS-attacker

Även om hastighetsbegränsning kan bidra till att minska risken för DoS-attacker (Denial of Service) genom att begränsa den hastighet med vilken begäranden bearbetas, är det inte en heltäckande lösning för DDoS-attacker (Distributed Denial of Service). DDoS-attacker omfattar flera system som överbelastar en app med en flod av begäranden, vilket gör det svårt att hantera enbart hastighetsbegränsning.

För robust DDoS-skydd bör du överväga att använda en kommersiell DDoS-skyddstjänst. Dessa tjänster erbjuder avancerade funktioner som:

  • Traffic Analysis: Kontinuerlig övervakning och analys av inkommande trafik för att identifiera och minimera DDoS-attacker i realtid.
  • Skalbarhet: Möjligheten att hantera storskaliga attacker genom att distribuera trafik över flera servrar och datacenter.
  • Automated Mitigation: Automatiserade responsmekanismer för att snabbt blockera skadlig trafik utan manuella ingripanden.
  • Global Network: Ett globalt nätverk av servrar för att absorbera och minimera attacker närmare källan.
  • Konstanta uppdateringar: Kommersiella tjänster spårar och uppdaterar kontinuerligt sina skyddsmekanismer för att anpassa sig till nya och växande hot.

När du använder en molnvärdtjänst är DDoS-skydd vanligtvis tillgängligt som en del av värdlösningen, till exempel Azure Web Application Firewall, AWS Shield eller Google Cloud Armor. Dedikerade skydd är tillgängliga som brandväggar för webbprogram (WAF) eller som en del av en CDN-lösning, till exempel Cloudflare eller Akamai Kona Site Defender

Implementering av en kommersiell DDoS-skyddstjänst tillsammans med hastighetsbegränsning kan ge en omfattande försvarsstrategi som säkerställer stabilitet, säkerhet och prestanda för en app.

Använd hastighetsbegränsande mellanlager

Följande steg visar hur du använder hastighetsbegränsningen för mellanprogram i en ASP.NET Core-app:

  1. Konfigurera tjänster för hastighetsbegränsning.

I filen Program.cs konfigurerar du tjänsten för hastighetsbegränsning genom att lägga till lämpliga principer för hastighetsbegränsning. Principer kan antingen definieras som globala eller namngivna principer. I följande exempel tillåts 10 begäranden per minut per användare (identitet) eller globalt:

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

Namngivna principer måste uttryckligen tillämpas på sidorna eller slutpunkterna. I följande exempel läggs en princip för fast fönsterbegränsning med namnet "fixed" som vi lägger till i en slutpunkt senare:

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

Den globala begränsningen gäller automatiskt för alla slutpunkter när den konfigureras via alternativ. GlobalLimiter.

  1. Aktivera hastighetsbegränsning för mellanprogram

    I filen Program.cs aktiverar du hastighetsbegränsningen för mellanprogram genom att anropa UseRateLimiter:

app.UseRouting();

app.UseRateLimiter();

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

app.Run();

Tillämpa principer för hastighetsbegränsning på slutpunkter eller sidor

Tillämpa hastighetsbegränsning på WebAPI-slutpunkter

Använd en namngiven princip på slutpunkten eller gruppen, till exempel:


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

Tillämpa hastighetsbegränsning på MVC-styrenheter

Tillämpa de konfigurerade hastighetsbegränsningsprinciperna på specifika slutpunkter eller globalt. Om du till exempel vill tillämpa principen "fast" på alla kontrollantslutpunkter:

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

Tillämpa hastighetsbegränsning på Blazor appar på serversidan

Om du vill ange hastighetsbegränsning för alla appens routningsbara Razor komponenter anger du RequireRateLimiting med det hastighetsbegränsande principnamnet på MapRazorComponents-anropet i filen Program. I följande exempel tillämpas principen för hastighetsbegränsning med namnet "policy" :

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

Om du vill ange en princip för en enda dirigerbar Razor komponent eller en mapp med komponenter via en _Imports.razor fil tillämpas [EnableRateLimiting]-attributet med principnamnet. I följande exempel tillämpas principen för hastighetsbegränsning med namnet "override". Principen ersätter alla principer som för närvarande tillämpas på slutpunkten. Den globala begränsaren fortsätter att köra på slutpunkten när detta attribut är applicerat.

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

<h1>Counter</h1>

Attributet [EnableRateLimiting] tillämpas endast på en routbar komponent eller en mapp med komponenter via en _Imports.razor-fil, om RequireRateLimitinginte anropas på MapRazorComponents.

Attributet [DisableRateLimiting] används för att inaktivera hastighetsbegränsning för en dirigerbar komponent eller en mapp med komponenter via en _Imports.razor fil.

Frekvensbegränsningsalgoritmer

Klassen RateLimiterOptionsExtensions innehåller följande tilläggsmetoder för hastighetsbegränsning:

De fasta, glidande och tokenbegränsarna begränsar alla det maximala antalet begäranden under en tidsperiod. Samtidighetsbegränsningen begränsar endast antalet samtidiga begäranden och begränsar inte antalet begäranden under en tidsperiod. Kostnaden för en slutpunkt bör beaktas när du väljer en begränsning. Kostnaden för en slutpunkt omfattar de resurser som används, till exempel tid, dataåtkomst, CPU och I/O.

Fast fönstergränsare

Metoden AddFixedWindowLimiter använder ett fast tidsfönster för att begränsa begäranden. När tidsfönstret upphör att gälla startar ett nytt tidsfönster och begärandegränsen återställs.

Överväg följande kod:

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

Föregående kod:

  • Anropar AddRateLimiter för att lägga till en tjänst för hastighetsbegränsning i tjänstsamlingen.
  • Anropar AddFixedWindowLimiter för att skapa en fast fönsterbegränsare med policy-namn "fixed" och anger:
  • PermitLimit till 4 och tiden Window till 12. Högst 4 begäranden per 12 sekunders fönster tillåts.
  • QueueProcessingOrder till OldestFirst.
  • QueueLimit till 2 (ange detta till 0 för att inaktivera kömekanismen).
  • Anropar UseRateLimiter för att aktivera hastighetsbegränsning.

Appar bör använda Configuration för att ange begränsningsalternativ. Följande kod uppdaterar föregående kod med hjälp av MyRateLimitOptions för konfiguration:

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 måste anropas efter UseRouting när hastighetsbegränsning av slutpunktsspecifika API:er används. Om attributet [EnableRateLimiting] till exempel används måste UseRateLimiter anropas efter UseRouting. När du bara anropar globala begränsare kan UseRateLimiter anropas innan UseRouting.

Skjutfönsterbegränsare

En algoritm för skjutfönster:

  • Liknar den fasta fönsterbegränsaren men lägger till segment per fönster. Fönstret glider ett segment varje segmentintervall. Segmentintervallet är (fönstertid)/(segment per fönster).
  • Begränsar begäranden för ett fönster till permitLimit begäranden.
  • Varje tidsfönster delas upp i n segment per fönster.
  • Begäranden som tas från det förfallna tidssegmentet ett fönster bakåt (n segment före det aktuella segmentet) läggs till i det aktuella segmentet. Vi avser det senaste utgångna tidssegmentet en period tillbaka som det utgångna segmentet.

Tänk på följande tabell som visar en glidande fönsterbegränsare med ett 30-sekundersfönster, tre segment per fönster och en gräns på 100 begäranden:

  • Den översta raden och den första kolumnen visar tidssegmentet.
  • Den andra raden visar återstående tillgängliga begäranden. Återstående begäranden beräknas som tillgängliga begäranden minus bearbetade begäranden plus de återvunna begärandena.
  • Begäranden rör sig hela tiden längs den diagonala blå linjen.
  • Från och med tidpunkt 30 läggs begäran från det utgångna tidssegmentet tillbaka till begäransgränsen, som visas i de röda linjerna.

Tabell som visar begäranden, gränser och återvunna fack

I följande tabell visas data i föregående diagram i ett annat format. Kolumnen Tillgänglig visar begäranden som är tillgängliga från föregående segment (Överför från föregående rad). Den första raden visar 100 tillgängliga begäranden eftersom det inte finns något tidigare segment.

Tid Tillgänglig Upptagen Återvinns från utgånget material Föra vidare
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

Följande kod använder den glidande fönsterhastighetsbegränsningen:

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-begränsare

Den s.k. "token bucket limiter" liknar den glidande fönsterbegränsaren, men i stället för att lägga till de förfrågningar som tas från den utgångna sektionen, läggs ett fast antal "tokens" till varje påfyllningsperiod. De tokens som lagts till för varje segment kan inte öka antalet tillgängliga tokens till ett tal som är högre än gränsen för token bucket. I följande tabell visas en token bucket limiter med en gräns på 100 token och en 10-sekunders påfyllningsperiod.

Tid Tillgänglig Tagit Lagt till Föra vidare
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

Följande kod använder token bucket limiter:

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

När AutoReplenishment är inställt på truefyller en intern timer på token varje ReplenishmentPeriod. När den är inställd på falsemåste appen anropa TryReplenish på begränsaren.

Samtidighetsbegränsning

Samtidighetsbegränsningen begränsar antalet samtidiga begäranden. Varje begäran minskar samtidighetsgränsen med en. När en begäran har slutförts ökas gränsen med en. Till skillnad från de andra begärandebegränsningarna som begränsar det totala antalet begäranden under en angiven period begränsar samtidighetsbegränsningen endast antalet samtidiga begäranden och begränsar inte antalet begäranden under en tidsperiod.

Följande kod använder samtidighetsbegränsningen:

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

Partitioner för hastighetsbegränsning

Hastighetsbegränsningspartitioner delar upp trafiken i separata "bucketar" som var och en får sina egna hastighetsgränsräknare. Detta ger mer detaljerad kontroll än en enda global räknare. Partitionens "buckets" definieras av olika nycklar (till exempel användar-ID, IP-adress eller API-nyckel).

Fördelar med partitionering

  • Fairness: En användare kan inte använda hela hastighetsgränsen för alla andra
  • Kornighet: Olika gränser för olika användare/resurser
  • Security: Bättre skydd mot riktat missbruk
  • nivåindelad tjänst: Stöd för tjänstnivåer med olika gränser

Partitionerad hastighetsbegränsning ger dig detaljerad kontroll över hur du hanterar API-trafik samtidigt som du säkerställer rättvis resursallokering.

Efter IP-adress

options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
    RateLimitPartition.GetFixedWindowLimiter(
        partitionKey: httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown",
        factory: _ => new FixedWindowRateLimiterOptions
        {
            PermitLimit = 50,
            Window = TimeSpan.FromMinutes(1)
        }));

Av användare 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)
        }));

Med API-nyckel

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

Enligt slutpunktssökväg

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

Skapa länkade begränsare

Med CreateChained-API:et kan du skicka in flera PartitionedRateLimiter som kombineras till en PartitionedRateLimiter. Den kombinerade begränsaren kör alla ingångsbegränsare i följd.

Följande kod använder 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();

Mer information finns i CreateChained-källkoden

Välja vad som ska hända när en begäran är hastighetsbegränsad

I enkla fall kan du bara ange statuskoden:

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

    // Rate limiter configuration...
});

Den vanligaste metoden är att registrera ett OnRejected-återanrop när du konfigurerar hastighetsbegränsning:

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

Ett annat alternativ är att ställa begäran i kö.

Köhantering av begäran

När köer är aktiverade och en begäran överskrider frekvensgränsen placeras begäran i en kö där den väntar tills ett tillstånd blir tillgängligt eller tills en tidsgräns uppnås. Begäranden bearbetas enligt en konfigurerbar köordning.

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

EnableRateLimiting- och DisableRateLimiting-attribut

Attributen [EnableRateLimiting] och [DisableRateLimiting] kan tillämpas på en kontrollant, åtgärdsmetod eller Razor sida. För Razor Pages måste attributet tillämpas på Razor-sidan och inte sidhanterare. Till exempel kan [EnableRateLimiting] inte tillämpas på OnGet, OnPosteller någon annan sidhanterare.

Attributet [DisableRateLimiting]inaktiverar hastighetsgränser till kontrollern, åtgärdsmetod eller Razor Sidan oavsett vilka namngivna begränsare eller globala begränsare som används. Tänk till exempel på följande kod som anropar RequireRateLimiting för att tillämpa fixedPolicy hastighetsbegränsning på alla kontrollantslutpunkter:

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

I följande kod inaktiverar [DisableRateLimiting] hastighetsbegränsning och åsidosätter [EnableRateLimiting("fixed")] som tillämpas på Home2Controller och app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy), anropade i 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 });
    }
}

I föregående kod tillämpas [EnableRateLimiting("sliding")]intePrivacy-åtgärdsmetoden eftersom Program.cs kallas app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy).

Överväg följande kod som inte anropar RequireRateLimitingMapRazorPages eller 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();

Tänk på följande kontrollant:

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

I den föregående kontrollern:

  • Den "fixed" policyhastighetsbegränsaren tillämpas på alla åtgärdsmetoder som inte har EnableRateLimiting- och DisableRateLimiting-attribut.
  • Policyräntebegränsningen "sliding" tillämpas på åtgärden Privacy.
  • Hastighetsbegränsning är inaktiverat på NoLimit-åtgärdsmetoden.

Frekvensbegränsningsmått

Det hastighetsbegränsande mellanprogrammet ger inbyggda mått och övervakningsfunktioner för att förstå hur begränsningar per tidsenhet påverkar appens prestanda och användarupplevelse. En lista över mått finns i Microsoft.AspNetCore.RateLimiting.

Testa slutpunkter med hastighetsbegränsning

Innan du distribuerar en app med hastighetsbegränsning till produktion ska du stresstesta appen för att verifiera de hastighetsbegränsningar och alternativ som används. Skapa till exempel ett JMeter-skript med ett verktyg som BlazeMeter eller Apache JMeter HTTP(S) Test Script Recorder och ladda skriptet till Azure Load Testing.

Genom att skapa partitioner med användarindata blir appen sårbar för DoS-attacker (Denial of Service). Om du till exempel skapar partitioner på klient-IP-adresser blir appen sårbar för Denial of Service-attacker som använder förfalskning av IP-källadresser. Mer information finns i BCP 38 RFC 2827 Nätverksingångsfiltrering: Att övervinna DoS-attacker som använder IP-källadress-spoofing.

Ytterligare resurser