Omezování rychlosti middlewaru v ASP.NET Core

Arvin Kahbazi, Maarten Balliauw a Rick Anderson

Middleware Microsoft.AspNetCore.RateLimiting poskytuje omezování rychlosti. Aplikace konfigurují zásady omezování rychlosti a pak zásady připojují ke koncovým bodům. Aplikace využívající omezování rychlosti by se měly před nasazením pečlivě testovat a kontrolovat. Další informace najdete v části Testování koncových bodů s omezováním rychlosti v tomto článku.

Úvod do omezování rychlosti najdete v tématu Omezování rychlosti middleware.

Proč používat omezování rychlosti

Omezení rychlosti se dá použít ke správě toku příchozích požadavků do aplikace. Klíčové důvody implementace omezování rychlosti:

  • Zabránění zneužití: Omezování rychlosti pomáhá chránit aplikaci před zneužitím omezením počtu žádostí, které může uživatel nebo klient provést v daném časovém období. Tato ochrana je zvláště důležitá pro veřejná rozhraní API.

  • Zajištění spravedlivého využití: Nastavením limitů, které uživatelům brání v monopolizaci systému, zajistíte, aby všichni uživatelé měli spravedlivý přístup k prostředkům.

  • Ochrana prostředků: Omezování rychlosti pomáhá zabránit přetížení serveru tím, že řídí počet požadavků, které lze zpracovat. Chrání backendové prostředky před zahlcením.

  • Zvýšení zabezpečení: Může zmírnit riziko útoků DoS (DoS) omezením rychlosti zpracování požadavků. Znesnadňuje útočníkům zatopit systém.

  • Zlepšení výkonu: Díky řízení rychlosti příchozích požadavků můžete zajistit optimální výkon a rychlost odezvy aplikace, což zajistí lepší uživatelské prostředí.

  • Cost Management: U služeb, které účtují náklady na základě využití, může omezení rychlosti pomoci spravovat a předpovídat výdaje kontrolou objemu zpracovávaných požadavků.

Implementace omezování rychlosti v aplikaci ASP.NET Core může pomoct udržet stabilitu, zabezpečení a výkon. Výsledkem je spolehlivá a efektivní služba pro všechny uživatele.

Prevence útoků DDoS

I když omezování rychlosti může pomoct zmírnit riziko útoků DoS (DoS) omezením rychlosti zpracování požadavků, nejedná se o komplexní řešení útoků DDoS (Distributed Denial of Service). Útoky DDoS zahrnují více systémů, které aplikaci zahlcují záplavou požadavků, což ztěžuje zpracování pomocí omezování rychlosti.

Pro robustní ochranu před útoky DDoS zvažte použití komerční služby ochrany před útoky DDoS. Tyto služby nabízejí pokročilé funkce, jako jsou:

  • Analýza provozu: Průběžné monitorování a analýza příchozího provozu za účelem detekce a zmírnění útoků DDoS v reálném čase.
  • škálovatelnosti: Schopnost zpracovávat rozsáhlé útoky prostřednictvím distribuce provozu napříč několika servery a datovými centry.
  • Automatizované zmírnění rizik: Mechanismy automatizované reakce pro rychlé blokování škodlivého provozu bez ručního zásahu.
  • Globální síť: Globální síť serverů pro absorbování a zmírnění útoků blíže ke zdroji.
  • Neustálé aktualizace: Komerční služby nepřetržitě sledují a aktualizují své mechanismy ochrany tak, aby se přizpůsobily novým a vyvíjejícím hrozbám.

Při použití služby hostování cloudu je ochrana před útoky DDoS obvykle k dispozici jako součást hostitelského řešení, jako je firewall webových aplikací Azure, AWS Shield nebo Google Cloud Armor. Vyhrazené ochrany jsou k dispozici jako firewally webových aplikací (WAF) nebo jako součást řešení CDN, jako je Cloudflare nebo Akamai Kona Site Defender

Implementace komerční služby ochrany před útoky DDoS ve spojení s omezováním rychlosti může poskytnout komplexní strategii ochrany, zajištění stability, zabezpečení a výkonu aplikace.

Použijte middleware pro omezení rychlosti

Následující kroky ukazují, jak používat middleware pro omezování rychlosti v aplikaci ASP.NET Core:

  1. Nakonfigurujte služby omezování rychlosti.

V souboru Program.cs nakonfigurujte služby omezování rychlosti přidáním příslušných zásad omezování rychlosti. Definujte zásady jako globální nebo pojmenované zásady. Následující příklad umožňuje 10 požadavků za minutu podle uživatele (identity) nebo globálně:

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

Pojmenované zásady je potřeba explicitně použít na stránky nebo koncové body. Následující příklad přidává zásadu omezování s pevným časovým oknem s názvem "fixed", kterou později přidáte ke koncovému bodu:

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

Globální limiter se použije pro všechny koncové body automaticky, když ho nakonfigurujete prostřednictvím možností. GlobalLimiter.

  1. Povolit middleware pro omezování rychlosti

    Ve souboru Program.cs povolte middleware omezování rychlosti voláním UseRateLimiter:

app.UseRouting();

app.UseRateLimiter();

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

app.Run();

Použití zásad omezování rychlosti na koncové body nebo stránky

Použití omezování rychlosti u koncových bodů webového rozhraní API

Použijte pojmenovanou zásadu na koncový bod nebo skupinu, například:


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

Použití omezování rychlosti na kontrolery MVC

Použijte nakonfigurované zásady omezování rychlosti na konkrétní koncové body nebo globálně. Pokud chcete například použít "pevné" zásady pro všechny koncové body kontroleru:

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

Přidat omezování rychlosti pro serverové aplikace Blazor

K nastavení omezování rychlosti pro všechny směrovatelné komponenty aplikace Razor zadejte RequireRateLimiting s názvem politika omezování rychlosti pro volání MapRazorComponents v souboru Program. V následujícím příkladu se použije zásada omezování rychlosti s názvem "policy":

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

Pokud chcete nastavit zásadu pro jednu směrovatelnou Razor komponentu nebo složku komponent prostřednictvím importového souboru (_Imports.razor), použijte [EnableRateLimiting] atribut s názvem zásady. V následujícím příkladu se použije zásada omezování rychlosti s názvem "override". Zásada nahrazuje všechny zásady, které se aktuálně vztahují na koncový bod. Globální limiter se stále spouští na koncovém bodu, kde je tento atribut použit.

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

<h1>Counter</h1>

[EnableRateLimiting] Použijte atribut pouze na směrovatelnou komponentu nebo složku komponent prostřednictvím importového souboru, pokud RequireRateLimitingnení volána MapRazorComponents.

Pomocí atributu[DisableRateLimiting] zakažte omezení rychlosti pro směrovatelnou komponentu nebo složku komponent prostřednictvím souboru importu.

Algoritmy omezování rychlosti

Třída RateLimiterOptionsExtensions poskytuje následující rozšiřující metody pro omezování rychlosti:

Pevné, posuvné a tokenové omezovače omezují maximální počet požadavků v časovém období. Limiter souběžnosti omezuje pouze počet souběžných požadavků a neskončuje počet požadavků v časovém období. Při výběru limitátoru zvažte náklady na koncový bod. Náklady na koncový bod zahrnují použité prostředky, jako je čas, přístup k datům, procesor a vstupně-výstupní operace.

Pevný okenní omezovač

Metoda AddFixedWindowLimiter používá k omezení požadavků pevný časový interval. Po vypršení časového intervalu se spustí nové časové okno a resetuje se limit požadavků.

Uvažujte následující kód:

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

Předchozí kód:

  • Volá AddRateLimiter, aby přidal službu omezování rychlosti do kolekce služeb.
  • Volá AddFixedWindowLimiter k vytvoření pevného omezovače oken s názvem "fixed" a nastaví:
  • PermitLimit až 4 a čas Window do 12. Je povoleno maximálně 4 požadavky na každé 12sekundové okno.
  • QueueProcessingOrder na OldestFirst.
  • QueueLimit na hodnotu 2 (nastavte na hodnotu 0, pokud chcete zakázat mechanismus zařadování do fronty).
  • Volání UseRateLimiter za účelem povolení omezování rychlosti.

Aplikace by měly používat konfiguraci k nastavení možností omezovače. Následující kód aktualizuje předchozí kód pomocí MyRateLimitOptions k aktualizaci konfigurace:

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 musí být voláno po UseRouting, když se používají rozhraní API pro omezení rychlosti specifická pro koncové body. Pokud je například atribut [EnableRateLimiting] použit, musí být UseRateLimiter zavoláno po UseRouting. Pokud se volají pouze globální limitery, UseRateLimiter se může volat před UseRouting.

Posuvný omezovač oken

Algoritmus posuvného okna:

  • Podobá se omezovači pevných oken, ale přidává segmenty na každé okno. Okno se posune o jeden segment při každém intervalu segmentů. Interval segmentu je (čas okna)/(segmenty na okno).
  • Omezuje počet požadavků na okno na permitLimit.
  • Každé časové okno je rozděleno do n segmentů na každé okno.
  • Žádosti převzaté z časového segmentu vypršeného o jedno okno zpět (n segmenty před aktuálním segmentem) se přidají do aktuálního segmentu. Jako segment s vypršenou platností označujeme nejstarší časový úsek, který je o jedno okno zpět.

Podívejte se na následující tabulku, která ukazuje omezovač posuvného okna s 30sekundovým oknem, třemi segmenty na okno a limitem 100 požadavků:

  • Horní řádek a první sloupec znázorňují časový segment.
  • Druhý řádek zobrazuje zbývající dostupné žádosti. Zbývající požadavky se počítají jako dostupné požadavky minus zpracované požadavky a recyklované žádosti.
  • Žádosti se v každém okamžiku pohybují podél diagonální modré čáry.
  • Od 30. okamžiku se žádost převzatá z časového segmentu s vypršenou platností přidá zpět do limitu požadavku, jak je znázorněno na červených řádcích.

Tabulka zobrazující požadavky, limity a recyklované sloty

Následující tabulka ukazuje data v předchozím grafu v jiném formátu. Sloupec K dispozici zobrazuje požadavky, které jsou dostupné z předchozího segmentu (přenos z předchozího řádku). První řádek zobrazuje 100 dostupných požadavků, protože neexistuje žádný předchozí segment.

Čas dostupný Obsazený Recyklováno z prošlých Přenést
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

Následující kód používá omezovač rychlosti posuvného okna:

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

Omezovač kontejnerů tokenů

Limitátor kontejneru tokenů je podobný klouzavému omezovači oken, ale místo přidání žádostí přijatých z segmentu s vypršenou platností se každý interval doplňování přidá pevný počet tokenů. Přidané tokeny v jednotlivých segmentech nemohou zvýšit dostupné tokeny na číslo vyšší než limit vědra tokenů. Následující tabulka ukazuje limit kontejneru tokenů s limitem 100 tokenů a 10sekundovým doplňovacím obdobím.

Čas dostupný Obsazený Přidáno Přenést
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

Následující kód používá omezovač kontejneru tokenů:

using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;

var builder = WebApplication.CreateBuilder(args);

var tokenPolicy = "token";
var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);

builder.Services.AddRateLimiter(_ => _
    .AddTokenBucketLimiter(policyName: tokenPolicy, options =>
    {
        options.TokenLimit = myOptions.TokenLimit;
        options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        options.QueueLimit = myOptions.QueueLimit;
        options.ReplenishmentPeriod = TimeSpan.FromSeconds(myOptions.ReplenishmentPeriod);
        options.TokensPerPeriod = myOptions.TokensPerPeriod;
        options.AutoReplenishment = myOptions.AutoReplenishment;
    }));

var app = builder.Build();

app.UseRateLimiter();

static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");

app.MapGet("/", () => Results.Ok($"Token Limiter {GetTicks()}"))
                           .RequireRateLimiting(tokenPolicy);

app.Run();

Když je AutoReplenishment nastaven na true, vnitřní časovač doplňuje tokeny každých ReplenishmentPeriod; když je nastaven na false, aplikace musí volat TryReplenish na limitator.

Omezovač souběžnosti

Limiter souběžnosti omezuje počet souběžných požadavků. Každý požadavek snižuje limit souběžnosti o jeden. Po dokončení žádosti se limit zvýší o jeden. Na rozdíl od ostatních omezovačů požadavků, které omezují celkový počet požadavků na zadané období, limiter souběžnosti omezuje pouze počet souběžných požadavků a nesdílí počet požadavků v časovém období.

Následující kód používá limiter souběžnosti:

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

Partitiony omezení rychlosti

Oddíly pro omezení rychlosti rozdělují provoz do samostatných skupin, z nichž každá má vlastní čítače omezení rychlosti. Tento přístup poskytuje podrobnější kontrolu než jeden globální čítač. Různé klíče, jako je ID uživatele, IP adresa nebo klíč rozhraní API, definují kontejnery oddílů.

Výhody dělení

  • Nestrannost: Jeden uživatel nemůže využívat celý limit rychlosti pro všechny.
  • Granularita: Různá omezení pro různé uživatele a zdroje.
  • Zabezpečení: Lepší ochrana před cílovým zneužitím.
  • Vrstvené služby: Podpora úrovní služby s různými omezeními

Omezení dělené rychlosti poskytuje jemně odstupňovanou kontrolu nad tím, jak spravujete provoz rozhraní API a zároveň zajišťuje spravedlivé přidělování prostředků.

Podle IP adresy

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

Podle identity uživatele

options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
    RateLimitPartition.GetFixedWindowLimiter(
        partitionKey: httpContext.User.Identity?.Name ?? "anonymous",
        factory: _ => new FixedWindowRateLimiterOptions
        {
            PermitLimit = 100,
            Window = TimeSpan.FromMinutes(1)
        }));

Podle klíče rozhraní API

options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
{
    string apiKey = httpContext.Request.Headers["X-API-Key"].ToString() ?? "no-key";

    // Different limits based on key tier
    return apiKey switch
    {
        "premium-key" => RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: apiKey,
            factory: _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 1000,
                Window = TimeSpan.FromMinutes(1)
            }),

        _ => RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: apiKey,
            factory: _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 100,
                Window = TimeSpan.FromMinutes(1)
            }),
    };
});

Podle cesty koncového bodu

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

Vytváření zřetězených omezovačů

Rozhraní CreateChained API přijímá více PartitionedRateLimiter instancí a kombinuje je do jednoho PartitionedRateLimiter. Kombinovaný limiter spouští všechny vstupní limitery v posloupnosti. Protože je výsledkem PartitionedRateLimiter přiřazené k GlobalLimiter, řetězec platí pro každý koncový bod. Pokud místo toho chcete zřetězit limitátory pro konkrétní koncový bod, použijte pojmenovanou zásadu, jak ukazuje Řetězení limitátorů v pojmenované zásadě.

Následující kód používá 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();

Další informace najdete ve zdrojovém kódu CreateChained.

Řetězení omezovačů v pojmenovaných zásadách

CreateChained řetězí globální omezovače, které se vztahují na všechny koncové body. Pokud chcete zkombinovat více typů omezovačů a omezit je na konkrétní koncové body, zřetězte omezení uvnitř pojmenované zásady pomocí CreateChained. Toto přetížení vrátí jeden RateLimiter , který spouští každý limiter v sekvenci, což je návratový typ, který vyžaduje objekt pro vytváření oddílů pojmenované zásady.

Následující "combined" zásada řetězí omezovač typu token bucket a omezovač souběžnosti pomocí RateLimiter.CreateChained a pak tuto zásadu použije na jeden koncový bod pomocí 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");

Žádost musí získat zapůjčení od každého omezovače v řetězci, aby bylo možné pokračovat, a omezovače běží v pořadí předaném RateLimiter.CreateChained. Pokud omezovač požadavek odmítne, je požadavek zamítnut a již získané příděly od předchozích omezovačů v řetězci se uvolní v opačném pořadí.

Při řetězení omezovačů rychlosti v pojmenované zásadě mějte na paměti následující skutečnosti:

  • Zrušení zapůjčení vrátí povolení pro limiter souběžnosti. Omezovače založené na čase (token bucket, pevné okno a posuvné okno) nevracejí oprávnění, které už bylo získáno, pokud některý z pozdějších omezovačů v řetězci požadavek odmítne, proto to zohledněte při řazení omezovačů v řetězci.
  • Přímé vytvoření TokenBucketRateLimiter s AutoReplenishment nastaveným na true poskytne každé instanci omezovače vlastní časovač. Pomocníci RateLimitPartition.GetTokenBucketLimiter a AddTokenBucketLimiter místo toho nastaví AutoReplenishment na false a obnoví všechny své limitery pomocí jediného sdíleného časovače.
  • RateLimiter.CreateChained neuvolňuje limitery, které jsou mu předány. V předchozím příkladu partition uchovává zřetězený omezovač v mezipaměti a framework spravuje jeho životnost. Pokud vytvoříte řetězené omezovače mimo partition, uvolněte vnitřní omezovače, jakmile se přestanou používat.
  • Upřednostňujte globální PartitionedRateLimiter.CreateChained přístup, když by se řetězec měl vztahovat na každý koncový bod. Používejte pojmenovanou zásadu s RateLimiter.CreateChained pouze v případě, že řetězec musí být omezen na konkrétní koncové body.

Volba toho, co se stane, když je požadavek omezený rychlostí

V jednoduchých případech stačí nastavit stavový kód:

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

    // Rate limiter configuration...
});

Nejběžnějším postupem je při konfiguraci omezení rychlosti zaregistrovat zpětné volání OnRejected:

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

Další možností je zařadit žádost do fronty:

Řízení front žádostí

Když povolíte řazení do fronty, pokud požadavek překročí limit rychlosti, systém ho umístí do fronty. Požadavek čeká ve frontě, dokud nebude povolení k dispozici nebo dojde k vypršení časového limitu. Systém zpracovává požadavky podle konfigurovatelného pořadí front.

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 a DisableRateLimiting atributy

Použijte atributy [EnableRateLimiting] a [DisableRateLimiting] u kontroleru, metody akce nebo stránky Razor. U Razor stránek použijte atribut na Razor stránku, nikoli na obslužné rutiny stránky. Například nelze použít [EnableRateLimiting] na OnGet, OnPost ani na žádnou jinou obslužnou rutinu stránky.

Atribut [DisableRateLimiting]zakáže omezení rychlosti pro kontroler, metodu akce nebo Razor stránku bez ohledu na pojmenované omezovače rychlosti nebo globální limitery použité. Představte si například následující kód, který volá RequireRateLimiting aby se rychlostní omezení použilo fixedPolicy na všechny koncové body kontroleru.

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

V následujícím kódu [DisableRateLimiting] deaktivuje omezení rychlosti a přepíše nastavení [EnableRateLimiting("fixed")], které se použije na Home2Controller a app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy) volané v 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 });
    }
}

V předchozím kódu se [EnableRateLimiting("sliding")]nepoužije na metodu akce Privacy, protože byla Program.cs volána app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy).

Zvažte následující kód, který nezavolá na RequireRateLimiting na MapRazorPages nebo 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();

Vezměte v úvahu následující kontroler:

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

V předchozím kontroleru:

  • Omezení rychlosti zásad "fixed" je použito na všechny metody akcí, které nemají atributy EnableRateLimiting a DisableRateLimiting.
  • Na "sliding" akci je aplikován Privacy omezovač rychlosti zásad.
  • Omezení rychlosti je u metody akce NoLimit zakázáno.

Metriky omezování rychlosti

Middleware pro omezování rychlosti poskytuje integrované metriky a možnosti monitorování , které vám pomůžou pochopit, jak omezení rychlosti ovlivňují výkon aplikací a uživatelské prostředí. Seznam metrik najdete v tématu Microsoft.AspNetCore.RateLimiting.

Testování koncových bodů s omezením rychlosti

Před nasazením aplikace, která používá omezení rychlosti do produkčního prostředí, otestujte aplikaci zátěží, abyste ověřili omezení rychlosti a možnosti, které jste použili. Můžete například vytvořit skript JMeter pomocí nástroje, jako je BlazeMeter nebo Apache JMeter HTTP(S) Test Script Recorder a načíst skript pro Azure Load Testing.

Pokud vytváříte oddíly pomocí uživatelského vstupu, stane se vaše aplikace zranitelnou vůči útokům odepření služby (DoS). Pokud například vytváříte oddíly pomocí IP adres klienta, bude vaše aplikace zranitelná vůči útokům na dostupnost služby, které používají falšování identity zdrojové IP adresy. Další informace najdete v tématu BCP 38 RFC 2827 Filtrování příchozího přenosu dat sítě: Porazit útoky na odepření služeb, které využívají falšování identity zdrojové IP adresy.

Další materiály