Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Przez Arvin Kahbazi, Maarten Balliauw i Rick Anderson
Oprogramowanie Microsoft.AspNetCore.RateLimiting
pośredniczące zapewnia ograniczanie szybkości. Aplikacje konfigurują zasady ograniczania szybkości, a następnie dołączają zasady do punktów końcowych. Aplikacje korzystające z ograniczania szybkości powinny być dokładnie testowane i sprawdzane przed wdrożeniem. Aby uzyskać więcej informacji, zobacz Testowanie punktów końcowych z ograniczaniem szybkości w tym artykule.
Aby zapoznać się z wprowadzeniem do ograniczania szybkości, zajrzyj do Ograniczania szybkości middleware.
Dlaczego warto używać ograniczania szybkości
Ograniczanie szybkości może służyć do zarządzania przepływem żądań przychodzących do aplikacji. Najważniejsze powody implementacji ograniczania szybkości:
- Zapobieganie nadużyciom: ograniczanie szybkości pomaga chronić aplikację przed nadużyciami, ograniczając liczbę żądań, które użytkownik lub klient może wykonać w danym okresie. Jest to szczególnie ważne w przypadku publicznych interfejsów API.
- zapewnienie sprawiedliwego użycia: określając limity, wszyscy użytkownicy mają sprawiedliwy dostęp do zasobów, uniemożliwiając użytkownikom monopolizowanie systemu.
- Ochrona zasobów: ograniczanie szybkości pomaga zapobiegać przeciążeniu serwera przez kontrolowanie liczby żądań, które można przetworzyć, co chroni zasoby zaplecza przed przeciążeniem.
- zwiększanie bezpieczeństwa: może ograniczyć ryzyko ataków typu "odmowa usługi" (DoS), ograniczając szybkość przetwarzania żądań, co utrudnia atakującym zalanie systemu.
- Poprawianie wydajności: kontrolując szybkość żądań przychodzących, można zachować optymalną wydajność i czas odpowiedzi aplikacji, zapewniając lepsze środowisko użytkownika.
- Cost Management: w przypadku usług, które generują koszty na podstawie użycia, ograniczanie liczby żądań może pomóc w zarządzaniu i przewidywaniu wydatków przez kontrolowanie ilości przetworzonych żądań.
Implementowanie ograniczania szybkości w aplikacji ASP.NET Core może pomóc zachować stabilność, bezpieczeństwo i wydajność, zapewniając niezawodną i wydajną usługę dla wszystkich użytkowników.
Zapobieganie atakom DDoS
Chociaż ograniczanie szybkości może pomóc w ograniczeniu ryzyka ataków typu "odmowa usługi" (DoS), ograniczając szybkość przetwarzania żądań, nie jest to kompleksowe rozwiązanie dla ataków DDoS (Distributed Denial of Service). Ataki DDoS obejmują wiele systemów przytłaczających aplikację powodzią żądań, co sprawia, że samo ograniczanie szybkości staje się niewystarczające.
Aby uzyskać niezawodną ochronę przed atakami DDoS, rozważ użycie komercyjnej usługi ochrony przed atakami DDoS. Te usługi oferują zaawansowane funkcje, takie jak:
- Analiza ruchu: ciągłe monitorowanie i analiza ruchu przychodzącego w celu wykrywania i eliminowania ataków DDoS w czasie rzeczywistym.
- skalowalność: możliwość obsługi ataków na dużą skalę przez dystrybucję ruchu między wieloma serwerami i centrami danych.
- zautomatyzowane środki zaradcze: automatyczne mechanizmy reagowania w celu szybkiego blokowania złośliwego ruchu bez ręcznej interwencji.
- Globalna Sieć: pewna globalna sieć serwerów do absorbowania i łagodzenia ataków bliżej źródła.
- stałe aktualizacje: usługi komercyjne stale śledzą i aktualizują swoje mechanizmy ochrony, aby dostosować się do nowych i zmieniających się zagrożeń.
W przypadku korzystania z usługi hostingu w chmurze ochrona przed atakami DDoS jest zwykle dostępna w ramach rozwiązania hostingu, takiego jak Azure Web Application Firewall, AWS Shield lub Google Cloud Armor. Dedykowane zabezpieczenia są dostępne jako zapory aplikacji internetowej (WAF) lub w ramach rozwiązania CDN, takiego jak Cloudflare lub Akamai Kona Site Defender
Zaimplementowanie komercyjnej usługi ochrony przed atakami DDoS w połączeniu z ograniczaniem szybkości może zapewnić kompleksową strategię obrony, zapewniając stabilność, bezpieczeństwo i wydajność aplikacji.
Użyj pośrednika do ograniczania szybkości
W poniższych krokach pokazano, jak używać oprogramowania pośredniczącego ograniczającego szybkość w aplikacji ASP.NET Core:
- Konfigurowanie usług ograniczania szybkości.
W pliku Program.cs
skonfiguruj usługi ograniczające szybkość, dodając odpowiednie zasady ograniczania szybkości. Zasady mogą być definiowane jako globalne lub nazwane zasady. Poniższy przykład zezwala na 10 żądań na minutę według użytkownika (tożsamości) lub globalnie:
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)
}));
});
Nazwane zasady muszą być jawnie stosowane do stron lub punktów końcowych. W poniższym przykładzie dodano stałe zasady ogranicznika okien o nazwie "fixed"
które dodamy do punktu końcowego później:
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();
Globalny limiter automatycznie stosuje się do wszystkich punktów końcowych, gdy jest skonfigurowany za pomocą opcji . GlobalLimiter.
Włączanie oprogramowania pośredniczącego ograniczającego szybkość
W pliku
Program.cs
włącz oprogramowanie pośredniczące ograniczające szybkość, wywołując UseRateLimiter:
app.UseRouting();
app.UseRateLimiter();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run();
Zastosuj zasady ograniczania szybkości do punktów końcowych lub stron
Stosowanie ograniczania szybkości do punktów końcowych interfejsu WebAPI
Zastosuj nazwane zasady do punktu końcowego lub grupy, na przykład:
app.MapGet("/api/resource", () => "This endpoint is rate limited")
.RequireRateLimiting("fixed"); // Apply specific policy to an endpoint
Stosowanie ograniczania szybkości do kontrolerów MVC
Zastosuj skonfigurowane zasady ograniczania szybkości do określonych punktów końcowych lub globalnie. Aby na przykład zastosować politykę "stałą" do wszystkich punktów końcowych kontrolera:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers().RequireRateLimiting("fixed");
});
Stosowanie ograniczania szybkości do aplikacji Blazor po stronie serwera
Aby ustawić ograniczanie szybkości dla wszystkich routowalnych składników Razor aplikacji, określ RequireRateLimiting z nazwą zasad ograniczania szybkości w wywołaniu MapRazorComponents w pliku Program
. W poniższym przykładzie zastosowano zasady ograniczania szybkości o nazwie "policy
":
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.RequireRateLimiting("policy");
Aby ustawić politykę dla pojedynczego routowalnego składnika Razor lub folderu składników za pośrednictwem pliku _Imports.razor
, atrybut [EnableRateLimiting]
jest stosowany z nazwą polityki. W poniższym przykładzie zastosowano zasadę ograniczania szybkości o nazwie "override
". Polityka zastępuje wszelkie polityki, które są obecnie stosowane na tym punkcie końcowym. Globalny limiter nadal działa w punkcie dostępu z zastosowaniem tego atrybutu.
@page "/counter"
@using Microsoft.AspNetCore.RateLimiting
@attribute [EnableRateLimiting("override")]
<h1>Counter</h1>
Atrybut [EnableRateLimiting]
jest stosowany tylko do składnika routingu lub folderu składników za pośrednictwem pliku _Imports.razor
, jeśli RequireRateLimiting nie jest wywoływany na MapRazorComponents.
Atrybut [DisableRateLimiting]
służy do wyłączania ograniczania szybkości dla składnika, który można trasować, lub dla folderu składników za pomocą pliku _Imports.razor
.
Algorytmy ogranicznika szybkości
Klasa RateLimiterOptionsExtensions
udostępnia następujące metody rozszerzenia na potrzeby ograniczania szybkości:
Stałe, przesuwane i limitatory tokenów ograniczają maksymalną liczbę żądań w danym okresie. Ogranicznik współbieżności ogranicza tylko liczbę współbieżnych żądań i nie ogranicza liczby żądań w danym okresie. Koszt punktu końcowego należy wziąć pod uwagę podczas wybierania ogranicznika. Koszt punktu końcowego obejmuje używane zasoby, na przykład czas, dostęp do danych, procesor CPU i operacje we/wy.
Stały ogranicznik okien
Metoda AddFixedWindowLimiter
używa stałego przedziału czasu w celu ograniczenia żądań. Po wygaśnięciu przedziału czasu zostanie uruchomione nowe okno czasowe i zostanie zresetowany limit żądania.
Spójrzmy na poniższy 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();
Poniższy kod:
- Wywołuje AddRateLimiter w celu dodania usługi ograniczania szybkości do kolekcji usług.
- Wywołania
AddFixedWindowLimiter
w celu utworzenia stałego limitatora okien z nazwą polityki"fixed"
i ustawieniami: - PermitLimit do 4 i czas Window do 12. Dozwolone jest maksymalnie 4 żądania na każde 12-sekundowe okno.
- QueueProcessingOrder do OldestFirst.
- QueueLimit na 2 (ustaw tę wartość na 0, aby wyłączyć mechanizm kolejkowania).
- Wywołuje metodę UseRateLimiter , aby włączyć ograniczanie szybkości.
Aplikacje powinny używać konfiguracji do ustawiania opcji ogranicznika. Poniższy kod aktualizuje powyższy kod przy użyciu polecenia MyRateLimitOptions
na potrzeby konfiguracji:
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 należy wywołać po UseRouting
, kiedy używane są interfejsy API specyficzne dla punktu końcowego do limitowania szybkości. Na przykład, jeśli używany jest atrybut [EnableRateLimiting]
, UseRateLimiter
musi zostać wywołane po UseRouting
. Podczas wywoływania tylko globalnych ograniczników, można wywołać UseRateLimiter
przed UseRouting
.
Ogranicznik okna przesuwanego
Algorytm okna przesuwanego:
- Jest podobny do stałego limitatora okien, ale dodaje segmenty na okno. Okno przesuwa się o jeden segment w każdym interwale segmentów. Interwał segmentu to (czas okna)/(segmenty na okno).
- Ogranicza żądania dla okna do
permitLimit
żądań. - Każde okno czasowe jest podzielone w
n
segmentach na okno. - Żądania pobrane z wygasłego segmentu czasowego o jedno okno wstecz (
n
segmenty sprzed bieżącego segmentu) są dodawane do bieżącego segmentu. Odnosimy się do najbardziej przeterminowanego segmentu czasu z jedno okno wstecz jako segmentu przeterminowanego.
Rozważmy poniższą tabelę, która przedstawia przesuwany ogranicznik okna z 30-sekundowym oknem, trzema segmentami na okno i limitem 100 żądań:
- Górny wiersz i pierwsza kolumna zawierają segment czasu.
- Drugi wiersz zawiera pozostałe dostępne żądania. Pozostałe żądania są obliczane jako dostępne żądania pomniejszone o przetworzone żądania oraz żądania z recyklingu.
- Żądania każdorazowo przesuwają się wzdłuż ukośnej niebieskiej linii.
- Od czasu 30 żądania pobrane z wygasłego przedziału czasowego są dodawane z powrotem do limitu żądań, jak pokazano na czerwonych liniach.
W poniższej tabeli przedstawiono dane w poprzednim grafie w innym formacie. W kolumnie Dostępne są wyświetlane żądania dostępne z poprzedniego segmentu (Przeniesienie z poprzedniego wiersza). Pierwszy wiersz zawiera 100 dostępnych żądań, ponieważ nie ma poprzedniego segmentu.
Czas | Dostępny | Zajęte | Odzyskane z przeterminowanych produktów | Przewoż |
---|---|---|---|---|
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 |
W poniższym kodzie jest używany ogranicznik szybkości okien przesuwnych:
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();
Ogranicznik zasobnika tokenu
Ogranicznik zasobnika tokenów jest podobny do ogranicznika okna przesuwanego, ale zamiast ponownego dodawania żądań pobranych z wygasłego segmentu, w każdym okresie uzupełniania dodawana jest stała liczba tokenów. Tokeny dodane przez poszczególne segmenty nie mogą zwiększyć dostępnych tokenów do liczby wyższej niż limit zasobnika tokenu. W poniższej tabeli przedstawiono limit zasobnika tokenu z limitem 100 tokenów i 10-sekundowym okresem uzupełniania.
Czas | Dostępny | Zajęte | Dodane | Przewoż |
---|---|---|---|---|
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 |
Poniższy kod używa ogranicznika zasobnika tokenu:
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();
Gdy AutoReplenishment jest ustawione na true
, wewnętrzny czasomierz uzupełnia tokeny co ReplenishmentPeriod; gdy jest ustawione na false
, aplikacja musi wywołać TryReplenish na limiterze.
Ogranicznik współbieżności
Ogranicznik współbieżności ogranicza liczbę współbieżnych żądań. Każde żądanie zmniejsza limit współbieżności o jeden. Po zakończeniu żądania limit zostanie zwiększony o jeden. W przeciwieństwie do innych ograniczników żądań, które ograniczają łączną liczbę żądań dla określonego okresu, limitator współbieżności ogranicza tylko liczbę współbieżnych żądań i nie ogranicza liczby żądań w danym okresie.
Poniższy kod używa ogranicznika współbieżności:
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();
Partycje ograniczania szybkości
Partycje ograniczające szybkość dzielą ruch na oddzielne "zasobniki", z których każda otrzymuje własne liczniki limitu szybkości. Umożliwia to bardziej szczegółową kontrolę niż pojedynczy licznik globalny. Partycje "buckets" są definiowane przez różne klucze (takie jak identyfikator użytkownika, adres IP lub klucz interfejsu API).
Zalety partycjonowania
- Fairness: Jeden użytkownik nie może korzystać z całego limitu zasobów dla wszystkich
- Granularność: różne limity dla różnych użytkowników/zasobów
- Security: lepsza ochrona przed ukierunkowanymi nadużyciami
- Usługa warstwowa: wsparcie dla poziomów usług z różnymi limitami
Ograniczanie szybkości partycjonowanej zapewnia szczegółową kontrolę nad sposobem zarządzania ruchem interfejsu API przy jednoczesnym zapewnieniu sprawiedliwej alokacji zasobów.
Według adresu IP
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
RateLimitPartition.GetFixedWindowLimiter(
partitionKey: httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown",
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 50,
Window = TimeSpan.FromMinutes(1)
}));
Przez użytkownika 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)
}));
Według klucza interfejsu 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)
}),
};
});
Według ścieżki końcowej
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)
});
});
Tworzenie ograniczników łańcuchowych
Interfejs API CreateChained umożliwia przekazywanie wielu PartitionedRateLimiter, które są łączone w jeden element PartitionedRateLimiter
. Połączony ogranicznik uruchamia wszystkie limitery wejściowe w sekwencji.
Poniższy kod używa metody 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();
Aby uzyskać więcej informacji, zobacz kod źródłowy CreateChained
Wybieranie tego, co się stanie, gdy żądanie jest ograniczone limitem
W prostych przypadkach można po prostu ustawić kod stanu:
builder.Services.AddRateLimiter(options =>
{
// Set a custom status code for rejections
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
// Rate limiter configuration...
});
Najczęstszym podejściem jest zarejestrowanie wywołania zwrotnego przy użyciu metody OnRejected podczas konfigurowania ograniczania szybkości:
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);
};
});
Inną opcją jest ustawienie żądania w kolejce:
Kolejkowanie żądań
Po włączeniu kolejkowania, gdy żądanie przekracza limit szybkości, jest umieszczane w kolejce, gdzie czeka, aż zezwolenie stanie się dostępne lub do momentu przekroczenia limitu czasu. Żądania są przetwarzane zgodnie z konfigurowalną kolejnością.
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
});
});
atrybuty EnableRateLimiting
i DisableRateLimiting
Atrybuty [EnableRateLimiting]
i [DisableRateLimiting]
można zastosować do kontrolera, metody akcji lub Razor strony. W przypadku Razor stron atrybut musi być stosowany do Razor Strony, a nie do procedur obsługi stron. Na przykład [EnableRateLimiting]
nie można zastosować do OnGet
, OnPost
ani żadnego innego programu obsługi stron.
Atrybut [DisableRateLimiting]
wyłącza ograniczanie szybkości kontrolera, metody akcji lub Razor strony niezależnie od nazwanych ograniczników szybkości lub globalnych ograniczników. Rozważmy na przykład następujący kod, który wywołuje RequireRateLimiting w celu zastosowania ograniczenia szybkości do wszystkich punktów końcowych kontrolera:
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();
W poniższym kodzie [DisableRateLimiting]
wyłącza ograniczanie szybkości i przesłania [EnableRateLimiting("fixed")]
stosowane do Home2Controller
i app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy)
wywoływanego w 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 });
}
}
W poprzednim kodzie element [EnableRateLimiting("sliding")]
nie jest stosowany do metody akcji , ponieważ wywołano Privacy
.
Rozważ następujący kod, który nie wywołuje RequireRateLimiting
na MapRazorPages
lub 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();
Rozważmy następujący 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 });
}
}
W poprzednim kontrolerze:
- Ogranicznik szybkości zasad
"fixed"
jest stosowany do wszystkich metod działania, które nie mają atrybutówEnableRateLimiting
lubDisableRateLimiting
. - Ogranicznik stawki polityki jest stosowany do działania
"sliding"
. - Ograniczanie szybkości jest wyłączone w metodzie
NoLimit
akcji.
Metryki ograniczania szybkości
Middleware ograniczające szybkość zapewnia wbudowane metryki i funkcje monitorowania, aby pomóc zrozumieć, w jaki sposób limity szybkości wpływają na wydajność aplikacji i doświadczenie użytkownika. Aby uzyskać listę metryk, zobacz Microsoft.AspNetCore.RateLimiting
.
Testowanie punktów końcowych z ograniczaniem szybkości
Przed wdrożeniem aplikacji przy użyciu ograniczania szybkości w środowisku produkcyjnym przetestuj aplikację w celu zweryfikowania używanych ograniczników szybkości i opcji. Na przykład utwórz skrypt JMeter za pomocą narzędzia takiego jak BlazeMeter lub Apache JMeter HTTP(S) Test Script Recorder i załaduj skrypt do testowania obciążenia platformy Azure.
Tworzenie partycji z danymi wejściowymi użytkownika sprawia, że aplikacja jest podatna na ataki typu "odmowa usługi " (DoS). Na przykład tworzenie partycji na adresach IP klienta sprawia, że aplikacja jest podatna na ataki typu "odmowa usługi", które korzystają z fałszowania adresów źródłowych IP. Aby uzyskać więcej informacji, zobacz Filtrowanie ruchu przychodzącego sieci BCP 38 RFC 2827: pokonanie ataków typu "odmowa usługi", które korzystają z fałszowania adresów źródłowych IP.
Dodatkowe zasoby
- Oprogramowanie pośredniczące do ograniczania limitu autorstwa Maartena Balliauwa zapewnia doskonałe wprowadzenie i przegląd ograniczania limitu.
- Ograniczenie prędkości programu obsługi HTTP na platformie .NET