作者:Arvin Kahbazi、Maarten Balliauw 和 Rick Anderson
Microsoft.AspNetCore.RateLimiting
中介軟體具有流量限制功能。 應用程式會設定速率限制原則,然後將原則附加至端點。 使用速率限制的應用程式應該先仔細進行載入測試及檢閱,然後再進行部署。 如需詳細資訊,請參閱本文中的測試具有速率限制的端點。
如需速率限制的簡介,請參閱速率限制中介軟體。
為何使用速率限制
速率限制可用來管理應用程式連入要求流程。 實作速率限制的主要原因:
- 防止濫用:速率限制可藉由限制使用者或用戶端在指定時間內提出的要求數目,協助保護應用程式免於濫用。 對於公用 API 來說,這特別重要。
- 確保公平使用:藉由設定限制,所有使用者都能公平存取資源,防止使用者壟斷系統。
- 保護資源:速率限制可藉由控制可處理的要求數目來協助防止伺服器多載,進而保護後端資源不受負擔。
- 增強安全性:藉由限制處理要求的速率,降低拒絕服務攻擊的風險,讓攻擊者更難淹沒系統。
- 改善效能:藉由控制傳入要求的速率,可以維護應用程式的最佳效能和回應性,以確保更好的用戶體驗。
- 成本管理:對於根據使用量產生成本的服務,速率限制可藉由控制處理的要求量來協助管理和預測費用。
在 ASP.NET Core 應用程式中實作速率限制,有助於維護穩定性、安全性和效能,確保所有使用者的可靠且有效率的服務。
防止 DDoS 攻擊
雖然速率限制可藉由限制處理要求的速率來協助降低阻斷服務 (DoS) 攻擊的風險,但這不是分散式阻斷服務 (DDoS) 攻擊的完整解決方案。 DDoS 攻擊牽涉到多個系統,讓應用程式遭受大量要求,因此難以單獨處理速率限制。
針對強固的 DDoS 保護,請考慮使用商業 DDoS 保護服務。 這些服務提供進階功能,例如:
- 流量分析:持續監視和分析傳入流量,以即時偵測及減輕 DDoS 攻擊。
- 延展性:將流量分散到多部伺服器和數據中心,以處理大規模攻擊的能力。
- 自動化風險降低:自動響應機制可快速封鎖惡意流量,而不需要手動介入。
- 全域網路:全域伺服器網路,可吸收並減輕離來源更近的攻擊。
- 常數更新:商業服務會持續追蹤並更新其保護機制,以適應新的和不斷演變的威脅。
使用雲端裝載服務時,DDoS 保護通常是作為裝載解決方案的一部分,例如 Azure Web 應用程式防火牆、AWS Shield 或 Google Cloud Armor。 專用保護可作為 Web 應用程式防火牆 (WAF) 或 CDN 解決方案的一部分,例如 Cloudflare 或 Akamai Kona Site Defender
實作商業 DDoS 保護服務搭配速率限制可以提供全面的防禦策略,確保應用程式的穩定性、安全性和效能。
使用速率限制中間件
下列步驟示範如何在 ASP.NET Core 應用程式中使用速率限制中間件:
- 設定速率限制服務。
在 Program.cs
檔案中,新增適當的速率限制原則來設定速率限制服務。 原則可以定義為全域或具名原則。 以下範例允許每位使用者(身份識別)或全域每分鐘 10 次請求:
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)
}));
});
具名原則必須明確套用至頁面或端點。 以下範例將新增一個固定視窗限制子原則,名稱為 "fixed"
,稍後我們將把它新增至一個端點:
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();
全域限制器會透過 選項設定時,自動套用至所有端點。GlobalLimiter。
啟用速率限制中間件
在
Program.cs
檔案中,藉由呼叫 UseRateLimiter來啟用速率限制中間件:
app.UseRouting();
app.UseRateLimiter();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run();
將速率限制原則套用至端點或頁面
將速率限制套用至 WebAPI 端點
將具名原則套用至端點或群組,例如:
app.MapGet("/api/resource", () => "This endpoint is rate limited")
.RequireRateLimiting("fixed"); // Apply specific policy to an endpoint
將速率限制套用至MVC控制器
將設定的速率限制原則套用至特定端點或全域。 例如,若要將「固定」原則套用至所有控制器端點:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers().RequireRateLimiting("fixed");
});
將速率限制套用至伺服器端 Blazor 應用程式
若要設定所有應用程式可路由傳送 Razor 元件的速率限制,請在 RequireRateLimiting 檔案的 MapRazorComponents 呼叫上指定速率限制原則名稱 Program
。 在下列範例中,會套用名為 「policy
」 的速率限制原則:
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.RequireRateLimiting("policy");
若要透過 Razor 檔案設定可路由的單一 _Imports.razor
元件或元件資料夾的政策,會在 [EnableRateLimiting]
屬性 上套用該政策名稱。 在下列範例中,會套用名為 「override
」 的速率限制原則。 原則會取代目前套用至端點的任何原則。 全域限制器仍會在套用此屬性的端點上執行。
@page "/counter"
@using Microsoft.AspNetCore.RateLimiting
@attribute [EnableRateLimiting("override")]
<h1>Counter</h1>
[EnableRateLimiting]
屬性 只會套用至可路由的元件或透過 _Imports.razor
檔案的元件資料夾,若未在 MapRazorComponents 上呼叫 RequireRateLimiting。
[DisableRateLimiting]
屬性 是用來透過 _Imports.razor
檔案停用可路由元件或元件資料夾的速率限制。
速率限制器演算法
RateLimiterOptionsExtensions
類別提供下列擴充方法以進行速率限制:
固定、滑動和權杖限制器都會限制一段時間內的要求數目上限。 並行限制器只會限制並行要求的數目,而不會限制一段時間內的要求數目。 選取限制器時,應該考慮端點的成本。 端點的成本包括所使用的資源,例如時間、資料存取、CPU 和 I/O。
固定視窗限制器
AddFixedWindowLimiter
方法會使用固定時間窗口來限制要求。 當視窗到期時,就會啟動新的視窗,並重設要求限制。
請考慮下列程式碼:
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();
上述 程式碼:
- 呼叫 AddRateLimiter 以將速率限制服務新增至服務集合。
- 呼叫
AddFixedWindowLimiter
,以建立具有原則名稱"fixed"
的固定視窗限制器,並設定相關配置: - 將 PermitLimit 設定為 4,並將時間 Window 設定為 12。 每個 12 秒視窗最多允許 4 個要求。
- QueueProcessingOrder 至 OldestFirst。
- QueueLimit 為 2(將此設定為 0 以停用佇列機制)。
- 呼叫 UseRateLimiter 以啟用速率限制。
應用程式應該使用組態來設定限制器選項。 下列程式碼使用 MyRateLimitOptions
作為配置來更新前述程式碼:
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();
在使用速率限制端點特定 API 時,必須在 UseRateLimiter 之後呼叫 UseRouting
。 例如,如果使用 [EnableRateLimiting]
屬性,則必須在 UseRateLimiter
之後呼叫 UseRouting
。 只呼叫全域限制器時,可以在 UseRateLimiter
之前呼叫 UseRouting
。
滑動視窗限制器
滑動視窗演算法:
- 類似於固定視窗限制器,但會為每個視窗新增區段。 視窗每個區段間隔滑動一次。 區段間隔為 (視窗時間)/(每個視窗的區段)。
- 將窗口可接受的要求數量限制為
permitLimit
之內。 - 每個時間視窗都被分成
n
個區段。 - 取自前一個窗口的已過期時間區段(即目前區段的前
n
區段)的要求會被新增至目前的區段。 我們將過期最久時間區段的前一個視窗稱為過期的區段。
請考慮下表,其中顯示滑動視窗限制器,包含 30 秒的視窗、每個視窗三個區段,以及 100 個要求的限制:
- 第一列和第一欄顯示時間段。
- 第二列會顯示剩餘可用的請求。 系統會計算其餘要求,以可用的要求減去已處理的要求加上回收的要求。
- 每一個請求都會沿著藍色對角線移動。
- 從時間 30 開始,來自過期的時間段的請求會被新增回請求限制,如紅線所示。
下表以不同的格式顯示上一個圖表中的資料。
時間 | 可用 | 被拿走 | 回收自過期的產品 | 繼續 |
---|---|---|---|---|
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 |
六十 | 50 | 35 | 30 | 45 |
下列程式碼使用滑動視窗速率限制器:
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();
令牌桶限制器
令牌桶限制器類似於滑動視窗限制器,但不是將從過期區段取出的請求添加回去,而是在每個補充期間新增固定數量的令牌。 每個部分新增的令牌無法使可用令牌增加到超過令牌桶限制的數量。 下表顯示權杖貯體限制器,其限制為 100 個權杖和 10 秒的補充期間。
時間 | 可用 | 被拿走 | 已新增 | 繼續 |
---|---|---|---|---|
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 |
以下程式碼會使用令牌桶限制器:
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 設定為 true
時,內部計時器會每 ReplenishmentPeriod 補充權杖;當設定為 false
時,應用程式必須在限制器上呼叫 TryReplenish。
並行限制器
並行限制器會限制並行要求的數目。 每個要求都會將併發限制減少一個。 當要求完成時,限制就會增加一個。 不同於其他限制指定期間要求總數的要求限制器,並行限制器只會限制並行要求的數目,且不會限制一段時間內的要求數目。
下列程式碼使用並行限制器:
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();
速率限制分區
速率限制分區用來將流量劃分為獨立的「桶子」,每個桶子都有自己的速率限制計數器。 這允許比單一全域計數器更細微的控制。 分割區「桶子」是由不同的鍵定義(例如使用者 ID、IP 位址或 API 金鑰)。
分割的優點
- 公平性:一位用戶無法耗盡屬於所有人的整體速率限制
- 粒度:針對不同使用者/資源設置不同的限制
- 安全性:更妥善地防範目標濫用
- 階層式服務:支援不同限制的服務層級
分割速率限制可讓您更精細地控制如何管理 API 流量,同時確保公平的資源配置。
依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)
}));
由使用者 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)
}));
透過 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)
}),
};
});
依端點路徑
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)
});
});
建立鏈結限制器
CreateChained API 允許傳入多個 PartitionedRateLimiter,並結合成一個 PartitionedRateLimiter
。 合併的限制器會依序執行所有輸入限制器。
下列程式碼會使用 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();
如需詳細資訊,請參閱 CreateChained 原始程式碼
確定在請求被速率限制時的處理方式
針對簡單案例,您可以只設定狀態代碼:
builder.Services.AddRateLimiter(options =>
{
// Set a custom status code for rejections
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
// Rate limiter configuration...
});
在配置速率限制時,最常見的方法是註冊一個 OnRejected 回呼:
builder.Services.AddRateLimiter(options =>
{
// Rate limiter configuration...
options.OnRejected = async (context, cancellationToken) =>
{
// Custom rejection handling logic
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
context.HttpContext.Response.Headers["Retry-After"] = "60";
await context.HttpContext.Response.WriteAsync("Rate limit exceeded. Please try again later.", cancellationToken);
// Optional logging
logger.LogWarning("Rate limit exceeded for IP: {IpAddress}",
context.HttpContext.Connection.RemoteIpAddress);
};
});
另一個選項是將要求排入佇列:
要求佇列
啟用佇列時,當要求超過速率限制後,它會進入佇列中,等候許可變得可用或直到發生逾時為止。 要求會根據可設定的佇列順序進行處理。
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter("api", options =>
{
options.PermitLimit = 10; // Allow 10 requests
options.Window = TimeSpan.FromSeconds(10); // Per 10-second window
options.QueueLimit = 5; // Queue up to 5 additional requests
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; // Process oldest requests first
options.AutoReplenishment = true; // Default: automatically replenish permits
});
});
EnableRateLimiting
與 DisableRateLimiting
屬性
[EnableRateLimiting]
和 [DisableRateLimiting]
屬性可以套用至控制器、動作方法或 Razor Page。 對於 Razor Pages,屬性必須套用至 Razor Page,而不是頁面處理程序。 例如,[EnableRateLimiting]
無法套用至 OnGet
、OnPost
或任何其他頁面處理常式。
[DisableRateLimiting]
屬性會停用控制器、動作方法或Razor頁面的速率限制,而不論已套用具名速率限制器或全域限制器。 例如,請考慮下列程式碼,其會呼叫 RequireRateLimiting,將 fixedPolicy
速率限制套用至所有控制器端點:
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();
在下列程式碼中,[DisableRateLimiting]
會停用速率限制,並覆寫套用至 [EnableRateLimiting("fixed")]
的 Home2Controller
,並在 app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy)
中呼叫 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 });
}
}
在上述程式碼中,[EnableRateLimiting("sliding")]
不會套用至 Privacy
動作方法,因為 Program.cs
稱為 app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy)
。
請考慮下列未在 RequireRateLimiting
或 MapRazorPages
上呼叫 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();
請考慮下列控制器:
[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 });
}
}
在上述控制器中:
-
"fixed"
原則速率限制器會套用至沒有EnableRateLimiting
和DisableRateLimiting
屬性的所有動作方法。 -
"sliding"
原則速率限制器會套用至Privacy
動作。 - 已停用
NoLimit
動作方法上的速率限制。
速率限制指標
速率限制中間件提供 內建計量和監視 功能,以協助瞭解速率限制如何影響應用程式效能和用戶體驗。 如需計量清單,請參閱 Microsoft.AspNetCore.RateLimiting
。
使用限制速率測試端點
使用速率限制將應用程式部署到生產環境之前,壓力測試應用程式以驗證所使用的速率限制器和選項。 例如,使用 BlazeMeter 或 Apache JMeter HTTP(S) 測試指令碼錄製器之類的工具來建立 JMeter 指令碼,並將指令碼載入 Azure 負載測試。
使用使用者輸入建立分割區,可讓應用程式容易受到拒絕服務 (DoS) 的攻擊。 例如,在用戶端 IP 位址上建立分割區,可讓應用程式容易受到採用 IP 來源位址詐騙的拒絕服務攻擊。 如需詳細資訊,請參閱 BCP 38 RFC 2827 網路輸入篩選:拒絕採用 IP 來源位址詐騙的服務攻擊。
其他資源
- Maarten Balliauw 的速率限制中介軟體提供絕佳的速率限制簡介和概觀。
- 在 .NET 中限制 HTTP 處理程序的速度