共用方式為


ASP.NET Core 中的速率限制中介軟體

作者:Arvin KahbaziMaarten BalliauwRick Anderson

Microsoft.AspNetCore.RateLimiting 中介軟體具有流量限制功能。 應用程式會設定速率限制原則,然後將原則附加至端點。 使用速率限制的應用程式應該先仔細進行載入測試及檢閱,然後再進行部署。 如需詳細資訊,請參閱本文中的測試具有速率限制的端點

如需速率限制的簡介,請參閱速率限制中介軟體

為何使用速率限制

速率限制可用來管理應用程式連入要求流程。 實作速率限制的主要原因:

  • 防止濫用:速率限制可藉由限制使用者或用戶端在指定時間內提出的要求數目,協助保護應用程式免於濫用。 對於公用 API 來說,這特別重要。
  • 確保公平使用:藉由設定限制,所有使用者都能公平存取資源,防止使用者壟斷系統。
  • 保護資源:速率限制可藉由控制可處理的要求數目來協助防止伺服器多載,進而保護後端資源不受負擔。
  • 增強安全性:藉由限制處理要求的速率,降低拒絕服務攻擊的風險,讓攻擊者更難淹沒系統。
  • 改善效能:藉由控制傳入要求的速率,可以維護應用程式的最佳效能和回應性,以確保更好的用戶體驗。
  • 成本管理:對於根據使用量產生成本的服務,速率限制可藉由控制處理的要求量來協助管理和預測費用。

在 ASP.NET Core 應用程式中實作速率限制,有助於維護穩定性、安全性和效能,確保所有使用者的可靠且有效率的服務。

防止 DDoS 攻擊

雖然速率限制可藉由限制處理要求的速率來協助降低阻斷服務 (DoS) 攻擊的風險,但這不是分散式阻斷服務 (DDoS) 攻擊的完整解決方案。 DDoS 攻擊牽涉到多個系統,讓應用程式遭受大量要求,因此難以單獨處理速率限制。

針對強固的 DDoS 保護,請考慮使用商業 DDoS 保護服務。 這些服務提供進階功能,例如:

  • 流量分析:持續監視和分析傳入流量,以即時偵測及減輕 DDoS 攻擊。
  • 延展性:將流量分散到多部伺服器和數據中心,以處理大規模攻擊的能力。
  • 自動化風險降低:自動響應機制可快速封鎖惡意流量,而不需要手動介入。
  • 全域網路:全域伺服器網路,可吸收並減輕離來源更近的攻擊。
  • 常數更新:商業服務會持續追蹤並更新其保護機制,以適應新的和不斷演變的威脅。

使用雲端裝載服務時,DDoS 保護通常是作為裝載解決方案的一部分,例如 Azure Web 應用程式防火牆AWS ShieldGoogle Cloud Armor。 專用保護可作為 Web 應用程式防火牆 (WAF) 或 CDN 解決方案的一部分,例如 CloudflareAkamai Kona Site Defender

實作商業 DDoS 保護服務搭配速率限制可以提供全面的防禦策略,確保應用程式的穩定性、安全性和效能。

使用速率限制中間件

下列步驟示範如何在 ASP.NET Core 應用程式中使用速率限制中間件:

  1. 設定速率限制服務。

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

  1. 啟用速率限制中間件

    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 個要求。
  • QueueProcessingOrderOldestFirst
  • 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 開始,來自過期的時間段的請求會被新增回請求限制,如紅線所示。

顯示要求、限制和回收位置的表格

下表以不同的格式顯示上一個圖表中的資料。 可用 欄會顯示上一個區段中可用的請求(此為上一個資料列的「結轉」)。 第一行顯示 100 個可用的請求,因為沒有之前的區段。

時間 可用 被拿走 回收自過期的產品 繼續
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
    });
});

EnableRateLimitingDisableRateLimiting 屬性

[EnableRateLimiting][DisableRateLimiting] 屬性可以套用至控制器、動作方法或 Razor Page。 對於 Razor Pages,屬性必須套用至 Razor Page,而不是頁面處理程序。 例如,[EnableRateLimiting] 無法套用至 OnGetOnPost 或任何其他頁面處理常式。

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

請考慮下列未在 RequireRateLimitingMapRazorPages 上呼叫 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" 原則速率限制器會套用至沒有 EnableRateLimitingDisableRateLimiting 屬性的所有動作方法。
  • "sliding" 原則速率限制器會套用至 Privacy 動作。
  • 已停用 NoLimit 動作方法上的速率限制。

速率限制指標

速率限制中間件提供 內建計量和監視 功能,以協助瞭解速率限制如何影響應用程式效能和用戶體驗。 如需計量清單,請參閱 Microsoft.AspNetCore.RateLimiting

使用限制速率測試端點

使用速率限制將應用程式部署到生產環境之前,壓力測試應用程式以驗證所使用的速率限制器和選項。 例如,使用 BlazeMeterApache JMeter HTTP(S) 測試指令碼錄製器之類的工具來建立 JMeter 指令碼,並將指令碼載入 Azure 負載測試

使用使用者輸入建立分割區,可讓應用程式容易受到拒絕服務 (DoS) 的攻擊。 例如,在用戶端 IP 位址上建立分割區,可讓應用程式容易受到採用 IP 來源位址詐騙的拒絕服務攻擊。 如需詳細資訊,請參閱 BCP 38 RFC 2827 網路輸入篩選:拒絕採用 IP 來源位址詐騙的服務攻擊

其他資源