ASP.NET Core'da hız sınırlama ara yazılımı
Arvin Kahbazi, Maarten Balliauw ve Rick Anderson tarafından
Ara Microsoft.AspNetCore.RateLimiting
yazılım, hız sınırlama ara yazılımı sağlar. Uygulamalar hız sınırlama ilkelerini yapılandırıp ilkeleri uç noktalara ekler. Hız sınırlama kullanan uygulamalar, dağıtmadan önce dikkatle yüklenmelidir ve gözden geçirilmelidir. Daha fazla bilgi için bu makaledeki Hız sınırlaması ile uç noktaları test etme konusuna bakın.
Hız sınırlamaya giriş için bkz . Hız sınırlama ara yazılımı.
Hız sınırlayıcı algoritmaları
sınıfı hız RateLimiterOptionsExtensions
sınırlama için aşağıdaki uzantı yöntemlerini sağlar:
Sabit pencere sınırlayıcısı
yöntemi, AddFixedWindowLimiter
istekleri sınırlamak için sabit bir zaman penceresi kullanır. Zaman penceresinin süresi dolduğunda yeni bir zaman penceresi başlatılır ve istek sınırı sıfırlanır.
Aşağıdaki kodu inceleyin:
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();
Yukarıdaki kod:
- Hizmet koleksiyonuna hız sınırlama hizmeti eklemek için çağrılar AddRateLimiter .
- İlke adı
"fixed"
ve olan sabit bir pencere sınırlayıcısı oluşturmak için çağrılarAddFixedWindowLimiter
: - PermitLimit 4'e ve 12'ye kadar olan süre Window . Her 12 saniyelik pencere başına en fazla 4 isteğe izin verilir.
- QueueProcessingOrder için OldestFirst.
- QueueLimit 2'ye kadar.
- Hız sınırlamayı etkinleştirmek için UseRateLimiter'ı çağırır.
Uygulamalar sınırlayıcı seçenekleri ayarlamak için Yapılandırma'yı kullanmalıdır. Aşağıdaki kod, yapılandırma için kullanarak MyRateLimitOptions
önceki kodu güncelleştirir:
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 hız sınırlama uç noktasına özgü API'ler kullanıldıktan sonra UseRouting
çağrılmalıdır. Örneğin, özniteliği kullanılıyorsa [EnableRateLimiting]
, UseRateLimiter
sonra UseRouting
çağrılmalıdır. Yalnızca genel sınırlayıcılar çağrılırken, UseRateLimiter
öncesinde UseRouting
çağrılabilir.
Kayan pencere sınırlayıcısı
Kayan pencere algoritması:
- Sabit pencere sınırlayıcısına benzer ancak pencere başına segmentler ekler. Pencere, her segment aralığı için bir segment kaydırıyor. Segment aralığı (pencere zamanı)/(pencere başına segmentler) aralığıdır.
- Pencere
permitLimit
isteklerini isteklerle sınırlar. - Her zaman penceresi pencere başına segmentlere bölünür
n
. - Süresi dolan zaman segmentinden bir pencere geri alınan istekler (
n
geçerli segmentten önceki segmentler) geçerli segmente eklenir. Son kullanma tarihi geçmiş segment olarak bir pencere geri dönerek en çok süresi dolmuş zaman segmentine başvuruyoruz.
30 saniyelik pencere, pencere başına üç segment ve 100 istek sınırı içeren kayan pencere sınırlayıcısını gösteren aşağıdaki tabloyu göz önünde bulundurun:
- Üst satır ve ilk sütunda zaman kesimi gösterilir.
- İkinci satır, kullanılabilir kalan istekleri gösterir. Kalan istekler, kullanılabilir istekler eksi işlenen istekler ve geri dönüştürülen istekler olarak hesaplanır.
- İstekler her seferinde çapraz mavi çizgi boyunca hareket eder.
- 30'dan itibaren süresi dolan zaman kesiminden alınan istek, kırmızı çizgilerde gösterildiği gibi istek sınırına geri eklenir.
Aşağıdaki tabloda, önceki grafikteki veriler farklı bir biçimde gösterilmiştir. Kullanılabilir sütunu, önceki segmentten (Önceki satırdan taşıma) sağlanan istekleri gösterir. İlk satırda 100 kullanılabilir istek gösterilir çünkü önceki bir segment yoktur.
Saat | Kullanılabilir | Alınan | Süresi dolan geri dönüştürüldü | Taşıma |
---|---|---|---|---|
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 |
Aşağıdaki kod kayan pencere hızı sınırlayıcısını kullanır:
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();
Belirteç demet sınırlayıcısı
Belirteç demet sınırlayıcısı kayan pencere sınırlayıcısına benzer, ancak süresi dolan segmentten alınan istekleri geri eklemek yerine, her yenileme dönemi için sabit sayıda belirteç eklenir. Her segmente eklenen belirteçler, kullanılabilir belirteçleri belirteç demet sınırından daha yüksek bir sayıya yükseltemez. Aşağıdaki tabloda, 100 belirteç sınırı ve 10 saniyelik yenileme süresi olan bir belirteç demet sınırlayıcısı gösterilmektedir.
Saat | Kullanılabilir | Alınan | Eklendi | Taşıma |
---|---|---|---|---|
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 |
Aşağıdaki kod belirteç demet sınırlayıcısını kullanır:
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();
AutoReplenishment olarak ayarlandığındatrue
, iç zamanlayıcı belirteçleri her ReplenishmentPeriodbiri olarak yeniler; olarak ayarlandığındafalse
, uygulamanın sınırlayıcıda çağrısı TryReplenish yapması gerekir.
Eşzamanlılık sınırlayıcısı
Eşzamanlılık sınırlayıcısı eşzamanlı istek sayısını sınırlar. Her istek eşzamanlılık sınırını bir azaltır. İstek tamamlandığında, sınır bir artırılır. Belirli bir süre için toplam istek sayısını sınırlayan diğer istek sınırlayıcılarından farklı olarak, eşzamanlılık sınırlayıcısı yalnızca eşzamanlı istek sayısını sınırlar ve bir zaman aralığındaki istek sayısını sınırlamaz.
Aşağıdaki kod eşzamanlılık sınırlayıcısını kullanır:
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();
Zincirlenmiş sınırlayıcılar oluşturma
API, CreateChained tek bir PartitionedRateLimiter
içine birleştirilen birden çok PartitionedRateLimiter öğesinin geçirilmesine izin verir. Birleşik sınırlayıcı, tüm giriş sınırlayıcılarını sırayla çalıştırır.
Aşağıdaki kodda kullanılır CreateChained
:
using System.Globalization;
using System.Threading.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRateLimiter(_ =>
{
_.OnRejected = (context, _) =>
{
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.Response.WriteAsync("Too many requests. Please try again later.");
return new ValueTask();
};
_.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();
Daha fazla bilgi için bkz . CreateChained kaynak kodu
EnableRateLimiting
ve DisableRateLimiting
öznitelikleri
[EnableRateLimiting]
ve [DisableRateLimiting]
öznitelikleri bir Denetleyiciye, eylem yöntemine veya Razor Sayfaya uygulanabilir. Sayfalar için Razor özniteliği sayfa işleyicilerine Razor değil Sayfaya uygulanmalıdır. Örneğin, [EnableRateLimiting]
, OnPost
veya başka bir sayfa işleyicisine OnGet
uygulanamaz.
özniteliği, [DisableRateLimiting]
adlandırılmış hız sınırlayıcılarına veya genel sınırlayıcılara bakılmaksızın Denetleyici, eylem yöntemi veya Razor Sayfa ile hız sınırlamayı devre dışı bırakır. Örneğin, hız sınırlamasını tüm denetleyici uç noktalarına uygulamak fixedPolicy
için çağıran RequireRateLimiting aşağıdaki kodu göz önünde bulundurun:
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();
Aşağıdaki kodda, [DisableRateLimiting]
hız sınırlamasını devre dışı bırakır ve içinde Program.cs
olarak adlandırılan ve app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy)
öğesine Home2Controller
uygulanan geçersiz kılmaları [EnableRateLimiting("fixed")]
devre dışı bırakır:
[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 });
}
}
Yukarıdaki kodda, [EnableRateLimiting("sliding")]
adlı app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy)
eylem yöntemine Program.cs
Privacy
uygulanmaz.
veya MapDefaultControllerRoute
üzerinde MapRazorPages
çağrılmayıp RequireRateLimiting
aşağıdaki kodu göz önünde bulundurun:
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();
Aşağıdaki denetleyiciyi göz önünde bulundurun:
[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 });
}
}
Önceki denetleyicide:
- İlke
"fixed"
hızı sınırlayıcısı, veDisableRateLimiting
öznitelikleri olmayanEnableRateLimiting
tüm eylem yöntemlerine uygulanır. - İlke
"sliding"
hızı sınırlayıcısı eylemePrivacy
uygulanır. - Eylem yönteminde
NoLimit
hız sınırlama devre dışı bırakıldı.
Sayfalar'a Razor öznitelik uygulama
Sayfalar için Razor özniteliği sayfa işleyicilerine Razor değil Sayfaya uygulanmalıdır. Örneğin, [EnableRateLimiting]
, OnPost
veya başka bir sayfa işleyicisine OnGet
uygulanamaz.
özniteliği, DisableRateLimiting
bir Razor Sayfada hız sınırlamayı devre dışı bırakır. EnableRateLimiting
yalnızca çağrılmadıysa sayfaya Razor MapRazorPages().RequireRateLimiting(Policy)
uygulanır.
Sınırlayıcı algoritması karşılaştırması
Sabit, kayan ve belirteç sınırlayıcılarının tümü, bir zaman aralığındaki en fazla istek sayısını sınırlar. Eşzamanlılık sınırlayıcısı yalnızca eşzamanlı istek sayısını sınırlar ve bir zaman aralığındaki istek sayısını sınırlamaz. Sınırlayıcı seçilirken uç noktanın maliyeti dikkate alınmalıdır. Bir uç noktanın maliyeti, zaman, veri erişimi, CPU ve G/Ç gibi kullanılan kaynakları içerir.
Hız sınırlayıcı örnekleri
Aşağıdaki örnekler üretim kodu için tasarlanmamıştır ancak sınırlayıcıların nasıl kullanılacağına ilişkin örneklerdir.
, RetryAfter
ve ile OnRejected
sınırlayıcıGlobalLimiter
Aşağıdaki örnek:
İstek belirtilen sınırı aştığında çağrılan bir RateLimiterOptions.OnRejected geri çağırması oluşturur.
retryAfter
,FixedWindowLimiter
veSlidingWindowLimiter
ileTokenBucketRateLimiter
kullanılabilir çünkü bu algoritmalar ne zaman daha fazla izin ekleneceğini tahmin edebilir. izinlerinConcurrencyLimiter
ne zaman kullanılabilir olacağını hesaplamanın hiçbir yolu yoktur.Aşağıdaki sınırlayıcıları ekler:
SampleRateLimiterPolicy
Arabirimi uygulayanIRateLimiterPolicy<TPartitionKey>
bir. SınıfıSampleRateLimiterPolicy
bu makalenin ilerleyen bölümlerinde gösterilir.- A
SlidingWindowLimiter
:- Kimliği doğrulanmış her kullanıcı için bir bölüm ile.
- Tüm anonim kullanıcılar için bir paylaşılan bölüm.
- GlobalLimiter Tüm isteklere uygulanan bir. Genel sınırlayıcı önce yürütülür, ardından varsa uç noktaya özgü sınırlayıcı yürütülür. ,
GlobalLimiter
her IPAddressiçin bir bölüm oluşturur.
// Preceding code removed for brevity.
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();
Uyarı
İstemci IP adreslerinde bölümler oluşturmak, uygulamayı IP Kaynak Adresi Kimlik Sahtekarlığı kullanan Hizmet Reddi Saldırılarına karşı savunmasız hale getirir. Daha fazla bilgi için bkz . BCP 38 RFC 2827 Ağ Girişi Filtreleme: IP Kaynak Adresi Kimlik Sahtekarlığına Neden Olan Hizmet Reddi Saldırılarını Önleme.
Dosyanın tamamı Program.cs
için örnek deposuna bakın.
Sınıfı 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
});
}
}
Yukarıdaki kodda, OnRejected yanıt durumunu 429 Çok Fazla İstek olarak ayarlamak için kullanırOnRejectedContext. Varsayılan reddedilen durum 503 Hizmet Kullanılamıyor şeklindedir.
Yetkilendirmeli sınırlayıcı
Aşağıdaki örnek JSON Web Belirteçlerini (JWT) kullanır ve JWT erişim belirteci ile bir bölüm oluşturur. Bir üretim uygulamasında JWT genellikle Güvenlik belirteci hizmeti (STS) olarak davranan bir sunucu tarafından sağlanabilir. Yerel geliştirme için, uygulamaya özgü yerel JWT'ler oluşturmak ve yönetmek için dotnet user-jwts komut satırı aracı kullanılabilir.
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}";
, TokenBucketRateLimiter
ve yetkilendirme ile ConcurrencyLimiter
sınırlayıcı
Aşağıdaki örnek:
- Sayfalar'da Razor kullanılan ilke adına
"get"
sahip birConcurrencyLimiter
ekler. TokenBucketRateLimiter
Her yetkili kullanıcı için bir bölüm ve tüm anonim kullanıcılar için bir bölüm ekler.- RateLimiterOptions.RejectionStatusCode'ı 429 Çok Fazla İstek olarak ayarlar.
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
});
}));
Dosyanın tamamı Program.cs
için örnek deposuna bakın.
Hız sınırlaması ile uç noktaları test etme
Üretimde hız sınırlaması kullanarak bir uygulamayı dağıtmadan önce, kullanılan hız sınırlayıcıları ve seçenekleri doğrulamak için uygulamayı stres testi yapın. Örneğin, BlazeMeter veya Apache JMeter HTTP(S) Test Betiği Kaydedicisi gibi bir araçla bir JMeter betiği oluşturun ve betiği Azure Load Testing'e yükleyin.
Kullanıcı girişiyle bölümler oluşturmak, uygulamayı Hizmet Reddi (DoS) Saldırılarına karşı savunmasız hale getirir. Örneğin, istemci IP adreslerinde bölümler oluşturmak, uygulamayı IP Kaynak Adresi Kimlik Sahtekarlığı kullanan Hizmet Reddi Saldırılarına karşı savunmasız hale getirir. Daha fazla bilgi için bkz . BCP 38 RFC 2827 Ağ Girişi Filtreleme: IP Kaynak Adresi Kimlik Sahtekarlığına Neden Olan Hizmet Reddi Saldırılarını Önleme.
Ek kaynaklar
- Maarten Balliauw tarafından sunulan hız sınırlama ara yazılımı , hız sınırlamaya mükemmel bir giriş ve genel bakış sağlar.
- .NET'te HTTP işleyicisi hız sınırı
ASP.NET Core