Ereignisse
Power BI DataViz Weltmeisterschaften
14. Feb., 16 Uhr - 31. März, 16 Uhr
Mit 4 Chancen, ein Konferenzpaket zu gewinnen und es zum LIVE Grand Finale in Las Vegas zu machen
Weitere InformationenDieser Browser wird nicht mehr unterstützt.
Führen Sie ein Upgrade auf Microsoft Edge durch, um die neuesten Features, Sicherheitsupdates und den technischen Support zu nutzen.
Von Arvin Kahbazi, Maarten Balliauw und Rick Anderson
Die Microsoft.AspNetCore.RateLimiting
-Middleware bietet Middleware mit Bandbreitenbegrenzung. Apps konfigurieren Richtlinien zur Begrenzung der Bandbreite und wenden dann die Richtlinien auf Endpunkten an. Apps mit Ratenbegrenzung sollten vor der Bereitstellung sorgfältigen Auslastungstests und Überprüfungen unterzogen werden. Weitere Informationen finden Sie unter Testen von Endpunkten mit Ratenbegrenzung in diesem Artikel.
Eine Einführung in die Ratelimitierung finden Sie unter Middleware mit Bandbreitenbegrenzung.
Die RateLimiterOptionsExtensions
-Klasse stellt die folgenden Erweiterungsmethoden für die Ratenbegrenzung bereit:
Die AddFixedWindowLimiter
-Methode verwendet ein festes Zeitfenster, um Anforderungen einzuschränken. Wenn das Zeitfenster abläuft, wird ein neues Zeitfenster gestartet, und die Anforderungsbegrenzung wird zurückgesetzt.
Betrachten Sie folgenden Code:
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRateLimiter(_ => _
.AddFixedWindowLimiter(policyName: "fixed", options =>
{
options.PermitLimit = 4;
options.Window = TimeSpan.FromSeconds(12);
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = 2;
}));
var app = builder.Build();
app.UseRateLimiter();
static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");
app.MapGet("/", () => Results.Ok($"Hello {GetTicks()}"))
.RequireRateLimiting("fixed");
app.Run();
Der vorangehende Code:
AddFixedWindowLimiter
wird aufgerufen, um eine Begrenzung mit festem Zeitfenster mit dem Richtliniennamen "fixed"
zu erstellen, und Folgendes wird festgelegt:Apps sollten die Konfiguration verwenden, um Begrenzungsoptionen festzulegen. Der folgende Code aktualisiert den vorherigen Code mithilfe von MyRateLimitOptions
für die 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 muss nach UseRouting
aufgerufen werden, wenn endpunktspezifische APIs für die Ratenbegrenzung verwendet werden. Wenn zum Beispiel das Attribut [EnableRateLimiting]
verwendet wird, muss UseRateLimiter
nach UseRouting
aufgerufen werden. Wenn nur globale Begrenzungen aufgerufen werden, UseRateLimiter
kann vor UseRouting
aufgerufen werden.
Für einen Algorithmus mit gleitendem Zeitfenster gilt Folgendes:
permitLimit
Anforderungen ein.n
Segmente pro Fenster unterteilt.n
Segmente vor dem aktuellen Segment) werden dem aktuellen Segment hinzugefügt. Wir bezeichnen das am frühesten abgelaufene Zeitsegment aus dem vorherigen Fenster als abgelaufenes Segment.Sehen Sie sich die folgende Tabelle an, die eine Begrenzung mit gleitendem Zeitfenster mit einem Fenster von 30 Sekunden, drei Segmenten pro Fenster und einem Grenzwert von 100 Anforderungen zeigt:
Die folgende Tabelle zeigt die Daten im vorherigen Diagramm in einem anderen Format. In der Spalte Verfügbar werden die Anforderungen angezeigt, die aus dem vorherigen Segment verfügbar sind (der Übertrag aus der vorherigen Zeile). Die erste Zeile zeigt 100 verfügbare Anforderungen an, da kein vorheriges Segment vorhanden ist.
Zeit | Verfügbar | Verwendet | Aus abgelaufenen wiederverwendet | Übertrag |
---|---|---|---|---|
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 |
Der folgende Code verwendet die Ratenbegrenzung mit gleitendem Zeitfenster:
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();
Die Tokenbucketbegrenzung ähnelt der Begrenzung mit gleitendem Zeitfenster. Statt jedoch die Anforderungen aus dem abgelaufenen Segment wieder hinzuzufügen, wird in jedem Auffüllungszeitraum eine feste Anzahl von Token hinzugefügt. Die in den einzelnen Segmenten hinzugefügten Token können die Anzahl verfügbarer Token nicht auf eine Zahl erhöhen, die die Tokenbucketbegrenzung übersteigt. Die folgende Tabelle zeigt eine Tokenbucketbegrenzung mit einem Grenzwert von 100 Token und einem Auffüllungszeitraum von 10 Sekunden.
Zeit | Verfügbar | Verwendet | Hinzugefügt | Übertrag |
---|---|---|---|---|
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 |
Der folgende Code verwendet die Tokenbucketbegrenzung:
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();
Wenn AutoReplenishment auf true
festgelegt ist, füllt ein interner Timer die Token in jeder ReplenishmentPeriod auf. Wenn die Option auf false
festgelegt ist, muss die App TryReplenish für die Begrenzung aufrufen.
Die Parallelitätsbegrenzung schränkt die Anzahl gleichzeitiger Anforderungen ein. Durch jede Anforderung wird die Parallelitätsbegrenzung um eins reduziert. Wenn eine Anforderung abgeschlossen ist, wird die Begrenzung um eins erhöht. Im Gegensatz zu anderen Anforderungsbegrenzungen, die die Gesamtanzahl von Anforderungen für einen angegebenen Zeitraum begrenzen, schränkt die Parallelitätsbegrenzung nur die Anzahl gleichzeitiger Anforderungen ein und begrenzt nicht die Anzahl der Anforderungen in einem bestimmten Zeitraum.
Der folgende Code verwendet die Parallelitätsbegrenzung:
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();
Die CreateChained-API ermöglicht das Übergeben mehrerer PartitionedRateLimiter, die zu einem PartitionedRateLimiter
kombiniert werden. Die kombinierte Begrenzung führt alle Eingabebegrenzungen nacheinander aus.
Der folgende Code verwendet 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();
Weitere Informationen finden Sie im CreateChained-Quellcode.
Die Attribute [EnableRateLimiting]
und [DisableRateLimiting]
können auf einen Controller, eine Aktionsmethode oder eine Razor-Seite angewendet werden. Für Razor Pages muss das Attribut auf die Razor-Seite und nicht auf die Seitenhandler angewendet werden. Beispielsweise kann [EnableRateLimiting]
nicht auf OnGet
, OnPost
oder andere Seitenhandler angewendet werden.
Das [DisableRateLimiting]
-Attribut deaktiviert die Ratenbegrenzung für den Controller, die Aktionsmethode oder die Razor-Seite, unabhängig von der Anwendung benannter Ratenbegrenzungen oder globaler Begrenzungen. Betrachten Sie beispielsweise den folgenden Code, der RequireRateLimiting aufruft, um die fixedPolicy
-Ratenbegrenzung auf alle Controllerendpunkte anzuwenden:
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();
Im folgenden Code deaktiviert [DisableRateLimiting]
die Ratenbegrenzung und setzt [EnableRateLimiting("fixed")]
außer Kraft, das auf Home2Controller
und app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy)
angewendet wird, die in Program.cs
aufgerufen werden:
[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 });
}
}
Im vorherigen Code wird [EnableRateLimiting("sliding")]
nicht auf die Privacy
-Aktionsmethode angewendet, da Program.cs
in app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy)
aufgerufen wurde.
Betrachten Sie den folgenden Code, der RequireRateLimiting
nicht für MapRazorPages
oder MapDefaultControllerRoute
aufruft:
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();
Betrachten Sie den folgenden Controller:
[EnableRateLimiting("fixed")]
public class Home2Controller : Controller
{
private readonly ILogger<Home2Controller> _logger;
public Home2Controller(ILogger<Home2Controller> logger)
{
_logger = logger;
}
public ActionResult Index()
{
return View();
}
[EnableRateLimiting("sliding")]
public ActionResult Privacy()
{
return View();
}
[DisableRateLimiting]
public ActionResult NoLimit()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
Im oben aufgeführten Controller gilt Folgendes:
"fixed"
wird auf alle Aktionsmethoden angewendet, die nicht über die Attribute EnableRateLimiting
und DisableRateLimiting
verfügen."sliding"
wird auf die Privacy
-Aktion angewendet.NoLimit
-Aktionsmethode deaktiviert.Für Razor Pages muss das Attribut auf die Razor-Seite und nicht auf die Seitenhandler angewendet werden. Beispielsweise kann [EnableRateLimiting]
nicht auf OnGet
, OnPost
oder andere Seitenhandler angewendet werden.
Das Attribut DisableRateLimiting
deaktiviert die Ratenbegrenzung für eine Razor-Seite. EnableRateLimiting
wird nur dann auf eine Razor-Seite angewendet, wenn MapRazorPages().RequireRateLimiting(Policy)
nicht aufgerufen wurde.
Die Begrenzungen mit festem Zeitfenster, gleitendem Zeitfenster und Tokenbucket begrenzen alle die maximale Anzahl von Anforderungen in einem bestimmten Zeitraum. Die Parallelitätsbegrenzung schränkt nur die Anzahl gleichzeitiger Anforderungen ein und begrenzt nicht die Gesamtanzahl der Anforderungen in einem Zeitraum. Die Kosten eines Endpunkts sollten bei der Auswahl einer Begrenzung berücksichtigt werden. Die Kosten eines Endpunkts umfassen die verwendeten Ressourcen, z. B. Zeit, Datenzugriff, CPU und E/A.
Die folgenden Beispiele sind nicht für Produktionscode bestimmt, sondern Beispiele für die Verwendung der Begrenzungen.
Im folgenden Beispiel geschieht Folgendes:
Ein RateLimiterOptions.OnRejected-Rückruf wird erstellt. Dieser wird aufgerufen, wenn eine Anforderung den angegebenen Grenzwert überschreitet. retryAfter
kann mit TokenBucketRateLimiter, FixedWindowLimiter und SlidingWindowLimiter verwendet werden, da diese Algorithmen abschätzen können, wann weitere Berechtigungen hinzugefügt werden. Der ConcurrencyLimiter
hat keine Möglichkeit zu berechnen, wann Genehmigungen verfügbar sein werden.
Folgende Begrenzungen werden hinzugefügt:
SampleRateLimiterPolicy
, die die IRateLimiterPolicy<TPartitionKey>
-Schnittstelle implementiert. Die SampleRateLimiterPolicy
-Klasse wird weiter unten in diesem Artikel gezeigt.SlidingWindowLimiter
gilt: GlobalLimiter
erstellt eine Partition für jede IPAddress.using System.Globalization;
using System.Net;
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRateLimitAuth;
using WebRateLimitAuth.Data;
using WebRateLimitAuth.Models;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ??
throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.Configure<MyRateLimitOptions>(
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit));
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
var userPolicyName = "user";
var helloPolicy = "hello";
var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
builder.Services.AddRateLimiter(limiterOptions =>
{
limiterOptions.OnRejected = (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;
context.HttpContext.RequestServices.GetService<ILoggerFactory>()?
.CreateLogger("Microsoft.AspNetCore.RateLimitingMiddleware")
.LogWarning("OnRejected: {GetUserEndPoint}", GetUserEndPoint(context.HttpContext));
return new ValueTask();
};
limiterOptions.AddPolicy<string, SampleRateLimiterPolicy>(helloPolicy);
limiterOptions.AddPolicy(userPolicyName, context =>
{
var username = "anonymous user";
if (context.User.Identity?.IsAuthenticated is true)
{
username = context.User.ToString()!;
}
return RateLimitPartition.GetSlidingWindowLimiter(username,
_ => new SlidingWindowRateLimiterOptions
{
PermitLimit = myOptions.PermitLimit,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = myOptions.QueueLimit,
Window = TimeSpan.FromSeconds(myOptions.Window),
SegmentsPerWindow = myOptions.SegmentsPerWindow
});
});
limiterOptions.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, IPAddress>(context =>
{
IPAddress? remoteIpAddress = context.Connection.RemoteIpAddress;
if (!IPAddress.IsLoopback(remoteIpAddress!))
{
return RateLimitPartition.GetTokenBucketLimiter
(remoteIpAddress!, _ =>
new TokenBucketRateLimiterOptions
{
TokenLimit = myOptions.TokenLimit2,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = myOptions.QueueLimit,
ReplenishmentPeriod = TimeSpan.FromSeconds(myOptions.ReplenishmentPeriod),
TokensPerPeriod = myOptions.TokensPerPeriod,
AutoReplenishment = myOptions.AutoReplenishment
});
}
return RateLimitPartition.GetNoLimiter(IPAddress.Loopback);
});
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseRateLimiter();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages().RequireRateLimiting(userPolicyName);
app.MapDefaultControllerRoute();
static string GetUserEndPoint(HttpContext context) =>
$"User {context.User.Identity?.Name ?? "Anonymous"} endpoint:{context.Request.Path}"
+ $" {context.Connection.RemoteIpAddress}";
static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");
app.MapGet("/a", (HttpContext context) => $"{GetUserEndPoint(context)} {GetTicks()}")
.RequireRateLimiting(userPolicyName);
app.MapGet("/b", (HttpContext context) => $"{GetUserEndPoint(context)} {GetTicks()}")
.RequireRateLimiting(helloPolicy);
app.MapGet("/c", (HttpContext context) => $"{GetUserEndPoint(context)} {GetTicks()}");
app.Run();
Warnung
Das Erstellen von Partitionen für Client-IP-Adressen macht die App anfällig für Denial-of-Service-Angriffe, die IP-Quelladressen-Spoofing verwenden. Weitere Informationen finden Sie unter BCP 38 RFC 2827 Netzwerkeingangsfilterung: Abwehr von Denial-of-Service-Angriffen mit IP-Quelladressen-Spoofing.
Die vollständige Program.cs
-Datei finden Sie im Beispielrepository.
Die Klasse SampleRateLimiterPolicy
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.Extensions.Options;
using WebRateLimitAuth.Models;
namespace WebRateLimitAuth;
public class SampleRateLimiterPolicy : IRateLimiterPolicy<string>
{
private Func<OnRejectedContext, CancellationToken, ValueTask>? _onRejected;
private readonly MyRateLimitOptions _options;
public SampleRateLimiterPolicy(ILogger<SampleRateLimiterPolicy> logger,
IOptions<MyRateLimitOptions> options)
{
_onRejected = (ctx, token) =>
{
ctx.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
logger.LogWarning($"Request rejected by {nameof(SampleRateLimiterPolicy)}");
return ValueTask.CompletedTask;
};
_options = options.Value;
}
public Func<OnRejectedContext, CancellationToken, ValueTask>? OnRejected => _onRejected;
public RateLimitPartition<string> GetPartition(HttpContext httpContext)
{
return RateLimitPartition.GetSlidingWindowLimiter(string.Empty,
_ => new SlidingWindowRateLimiterOptions
{
PermitLimit = _options.PermitLimit,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = _options.QueueLimit,
Window = TimeSpan.FromSeconds(_options.Window),
SegmentsPerWindow = _options.SegmentsPerWindow
});
}
}
Im vorherigen Code verwendet OnRejected die Funktion OnRejectedContext, um den Antwortstatus auf 429 Zu viele Anforderungen festzulegen. Der standardmäßige Ablehnungsstatus lautet 503 Dienst nicht verfügbar.
Das folgende Beispiel verwendet JSON Web Tokens (JWT) und erstellt eine Partition mit dem JWT-Zugriffstoken. In einer Produktions-App wird das JWT in der Regel von einem Server bereitgestellt, der als Sicherheitstokendienst (Security Token Service, STS) fungiert. Bei der lokalen Entwicklung kann das Befehlszeilentool dotnet user-jwts verwendet werden, um App-spezifische lokale JWTs zu erstellen und zu verwalten.
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Primitives;
using WebRateLimitAuth.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization();
builder.Services.AddAuthentication("Bearer").AddJwtBearer();
var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
var jwtPolicyName = "jwt";
builder.Services.AddRateLimiter(limiterOptions =>
{
limiterOptions.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
limiterOptions.AddPolicy(policyName: jwtPolicyName, partitioner: httpContext =>
{
var accessToken = httpContext.Features.Get<IAuthenticateResultFeature>()?
.AuthenticateResult?.Properties?.GetTokenValue("access_token")?.ToString()
?? string.Empty;
if (!StringValues.IsNullOrEmpty(accessToken))
{
return RateLimitPartition.GetTokenBucketLimiter(accessToken, _ =>
new TokenBucketRateLimiterOptions
{
TokenLimit = myOptions.TokenLimit2,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = myOptions.QueueLimit,
ReplenishmentPeriod = TimeSpan.FromSeconds(myOptions.ReplenishmentPeriod),
TokensPerPeriod = myOptions.TokensPerPeriod,
AutoReplenishment = myOptions.AutoReplenishment
});
}
return RateLimitPartition.GetTokenBucketLimiter("Anon", _ =>
new TokenBucketRateLimiterOptions
{
TokenLimit = myOptions.TokenLimit,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = myOptions.QueueLimit,
ReplenishmentPeriod = TimeSpan.FromSeconds(myOptions.ReplenishmentPeriod),
TokensPerPeriod = myOptions.TokensPerPeriod,
AutoReplenishment = true
});
});
});
var app = builder.Build();
app.UseAuthorization();
app.UseRateLimiter();
app.MapGet("/", () => "Hello, World!");
app.MapGet("/jwt", (HttpContext context) => $"Hello {GetUserEndPointMethod(context)}")
.RequireRateLimiting(jwtPolicyName)
.RequireAuthorization();
app.MapPost("/post", (HttpContext context) => $"Hello {GetUserEndPointMethod(context)}")
.RequireRateLimiting(jwtPolicyName)
.RequireAuthorization();
app.Run();
static string GetUserEndPointMethod(HttpContext context) =>
$"Hello {context.User.Identity?.Name ?? "Anonymous"} " +
$"Endpoint:{context.Request.Path} Method: {context.Request.Method}";
Im folgenden Beispiel geschieht Folgendes:
ConcurrencyLimiter
wird mit dem Richtliniennamen "get"
hinzugefügt, der auf den Razor-Seiten verwendet wird.TokenBucketRateLimiter
wird mit je einer Partition für die einzelnen autorisierten Benutzer*innen und einer Partition für alle anonymen Benutzer*innen hinzugefügt.var getPolicyName = "get";
var postPolicyName = "post";
var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
builder.Services.AddRateLimiter(_ => _
.AddConcurrencyLimiter(policyName: getPolicyName, options =>
{
options.PermitLimit = myOptions.PermitLimit;
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
})
.AddPolicy(policyName: postPolicyName, partitioner: httpContext =>
{
string userName = httpContext.User.Identity?.Name ?? string.Empty;
if (!StringValues.IsNullOrEmpty(userName))
{
return RateLimitPartition.GetTokenBucketLimiter(userName, _ =>
new TokenBucketRateLimiterOptions
{
TokenLimit = myOptions.TokenLimit2,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = myOptions.QueueLimit,
ReplenishmentPeriod = TimeSpan.FromSeconds(myOptions.ReplenishmentPeriod),
TokensPerPeriod = myOptions.TokensPerPeriod,
AutoReplenishment = myOptions.AutoReplenishment
});
}
return RateLimitPartition.GetTokenBucketLimiter("Anon", _ =>
new TokenBucketRateLimiterOptions
{
TokenLimit = myOptions.TokenLimit,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = myOptions.QueueLimit,
ReplenishmentPeriod = TimeSpan.FromSeconds(myOptions.ReplenishmentPeriod),
TokensPerPeriod = myOptions.TokensPerPeriod,
AutoReplenishment = true
});
}));
Die vollständige Program.cs
-Datei finden Sie im Beispielrepository.
Bevor Sie eine App mit Ratenbegrenzung in der Produktion bereitstellen, testen Sie die App, um die verwendeten Ratenbegrenzungen und -optionen zu validieren. Erstellen Sie beispielsweise ein JMeter-Skript mit einem Tool wie BlazeMeter oder Apache JMeter HTTP(S) Test Script Recorder, und laden Sie das Skript in Azure Load Testing.
Das Erstellen von Partitionen mit Benutzereingabe macht die App anfällig für Denial-of-Service-Angriffe (DoS). Durch das Erstellen von Partitionen für Client-IP-Adressen wird die App beispielsweise anfällig für Denial-of-Service-Angriffe, die IP-Quelladressen-Spoofing verwenden. Weitere Informationen finden Sie unter BCP 38 RFC 2827 Netzwerkeingangsfilterung: Abwehr von Denial-of-Service-Angriffen mit IP-Quelladressen-Spoofing.
Feedback zu ASP.NET Core
ASP.NET Core ist ein Open Source-Projekt. Wählen Sie einen Link aus, um Feedback zu geben:
Ereignisse
Power BI DataViz Weltmeisterschaften
14. Feb., 16 Uhr - 31. März, 16 Uhr
Mit 4 Chancen, ein Konferenzpaket zu gewinnen und es zum LIVE Grand Finale in Las Vegas zu machen
Weitere InformationenTraining
Modul
Schützen von APIs in Azure API Management - Training
Schützen Sie Ihre Back-End-APIs vor der Offenlegung von Informationen, und implementieren Sie Drosselungen (Ratenbegrenzung), um mit Richtlinien in Azure API Management eine Ressourcenüberlastung zu verhindern.