作成者: Arvin Kahbazi、Maarten Balliauw、Rick Anderson
Microsoft.AspNetCore.RateLimiting
ミドルウェアは、レート制限ミドルウェアです。 アプリでレート制限ポリシーを構成し、エンドポイントにポリシーをアタッチします。 レート制限を使用するアプリは、デプロイする前に慎重にロード テストし、確認する必要があります。 詳細については、この記事の「レート制限を使用したエンドポイントのテスト」を参照してください。
レート制限の概要については、「レート制限ミドルウェア」を参照してください。
レート制限を使用する理由
レート制限は、アプリへの受信要求のフローを管理するために使用できます。 レート制限を実装する主な理由:
- 不正使用の防止: レート制限は、ユーザーまたはクライアントが特定の期間内に行うことができる要求の数を制限することで、アプリを悪用から保護するのに役立ちます。 これは、パブリック API では特に重要です。
- 公平な使用の確保: 制限を設定することで、すべてのユーザーがリソースに公平にアクセスし、ユーザーがシステムを独占できないようにします。
- リソースの保護: レート制限は、処理できる要求の数を制御することでサーバーの過負荷を防ぐのに役立ち、バックエンド リソースが過負荷にならないように保護します。
- セキュリティの強化: 要求の処理速度を制限することで、サービス拒否 (DoS) 攻撃のリスクを軽減し、攻撃者がシステムをあふれさせるのを困難にすることができます。
- パフォーマンスの向上: 受信要求の速度を制御することで、アプリの最適なパフォーマンスと応答性を維持し、ユーザー エクスペリエンスを向上させることができます。
- Cost Management: 使用量に基づいてコストが発生するサービスの場合、レート制限は、処理される要求の量を制御することで、経費の管理と予測に役立ちます。
ASP.NET Core アプリにレート制限を実装すると、安定性、セキュリティ、パフォーマンスを維持し、すべてのユーザーに信頼性の高い効率的なサービスを提供できます。
DDoS 攻撃の防止
レート制限は、要求の処理速度を制限することでサービス拒否 (DoS) 攻撃のリスクを軽減するのに役立ちますが、分散型サービス拒否 (DDoS) 攻撃の包括的なソリューションではありません。 DDoS 攻撃では、複数のシステムが多数の要求でアプリを圧倒し、レート制限だけでは処理が困難になります。
堅牢な DDoS 保護の場合は、商用 DDoS 保護サービスの使用を検討してください。 これらのサービスは、次のような高度な機能を提供します。
- トラフィック分析: DDoS 攻撃をリアルタイムで検出して軽減するための受信トラフィックの継続的な監視と分析。
- スケーラビリティ: 複数のサーバーとデータ センターにトラフィックを分散することで大規模な攻撃を処理する機能。
- 自動化された軽減: 手動による介入なしで悪意のあるトラフィックをすばやくブロックする自動応答メカニズム。
- グローバル ネットワーク: ソースに近い攻撃を吸収して軽減するサーバーのグローバル ネットワーク。
- 継続的な更新: 商用サービスは、新しい脅威や進化する脅威に適応するために、保護メカニズムを継続的に追跡および更新します。
クラウド ホスティング サービスを使用する場合、DDoS 保護は通常、ホスティング ソリューションの一部として使用できます。たとえば、Azure Web Application Firewall、AWS Shield 、Google Cloud Armor などです。 専用保護は、Web アプリケーション ファイアウォール (WAF) として、または Cloudflare や Akamai Kona Site Defender などの CDN ソリューションの一部として使用
商用 DDoS 保護サービスをレート制限と組み合わせて実装すると、包括的な防御戦略が提供され、アプリの安定性、セキュリティ、パフォーマンスが確保されます。
レート制限ミドルウェアの使用
次の手順では、ASP.NET Core アプリでレート制限ミドルウェアを使用する方法を示します。
- レート制限サービスを構成します。
Program.cs
ファイルで、適切なレート制限ポリシーを追加してレート制限サービスを構成します。 ポリシーは、グローバル ポリシーまたは名前付きポリシーとして定義できます。 次の例では、ユーザー (ID) またはグローバルに 1 分あたり 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.
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");
ルーティング可能な 1 つの Razor コンポーネントまたはコンポーネントのフォルダーのポリシーを _Imports.razor
ファイル経由で設定するには、[EnableRateLimiting]
属性 がポリシー名と共に適用されます。 次の例では、"override
" という名前のレート制限ポリシーが適用されます。 このポリシーは、エンドポイントに現在適用されているポリシーを置き換えます。 グローバルリミッターは、この属性が適用されたエンドポイントで引き続き実行されます。
@page "/counter"
@using Microsoft.AspNetCore.RateLimiting
@attribute [EnableRateLimiting("override")]
<h1>Counter</h1>
[EnableRateLimiting]
属性は、_Imports.razor
が RequireRateLimiting で呼び出されたのではない場合に、ルーティング可能なコンポーネントまたはコンポーネント フォルダーにのみ、MapRazorComponents ファイルを介して適用されます。
[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
を呼び出すことができます。
スライディングウィンドウリミッター
スライディングウィンドウアルゴリズム:
- 固定時間枠リミッターに似ていますが、時間枠ごとにセグメントが追加されます。 ウィンドウは、セグメント間隔ごとに 1 つのセグメントをスライドします。 セグメント間隔は (時間枠の時間)/(1 つの時間枠あたりのセグメント数) です。
- 1 つの時間枠の要求件数を
permitLimit
件の要求に制限します。 - 各時間枠は、時間枠あたり
n
セグメントに分割されます。 - 1 つ前のウィンドウで期限切れとなった時間セグメントから取得した要求 (
n
セグメント前) は、現在のセグメントに追加されます。 1 つ前の時間枠で最も有効期限が切れている時間セグメントを、有効期限切れセグメントと呼びます。
30 秒の時間枠、1 時間枠あたり 3 つのセグメント、および 100 件の要求の制限を持つスライド式時間枠リミッターを示す次の表について考えてみましょう。
- 上部の行と最初の列には、時間セグメントが表示されます。
- 2 行目には、使用可能な残りの要求が表示されます。 残りの要求は、使用可能な要求から処理された要求とリサイクルされた要求を差し引いた値として計算されます。
- 要求は、各時点で斜めの青い線に沿って移動します。
- 時刻 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 |
60 | 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 を呼び出す必要があります。
コンカレンシー リミッター
コンカレンシー リミッターにより、同時実行要求の数は制限されます。 要求が 1 回行われると、コンカレンシーの制限が 1 つ減ります。 要求が完了すると、制限は 1 つ増やされます。 指定した期間の要求の合計数を制限する他の要求リミッターとは異なり、コンカレンシー リミッターは同時要求の数のみを制限し、一定期間の要求数の上限は制限しません。
次のコードでは、コンカレンシー リミッターを使用します:
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();
レート制限パーティション
レート制限パーティションは、トラフィックを個別の "バケット" に分割し、それぞれが独自のレート制限カウンターを取得します。 これにより、1 つのグローバル カウンターよりも細かい制御が可能になります。 パーティション "バケット" は、さまざまなキー (ユーザー ID、IP アドレス、API キーなど) によって定義されます。
パーティション分割の利点
- 公平性: 1 人のユーザーがすべてのユーザーに対してレート制限全体を使用することはできません
- 細分性: ユーザー/リソースごとに異なる制限を適用
- セキュリティ: 標的型不正使用に対する保護の強化
- 階層化サービス: 制限が異なるサービス レベルのサポート
パーティション分割されたレート制限により、公平なリソース割り当てを確保しながら、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 では、1 つの 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);
};
});
もう 1 つのオプションは、要求をキューに入れます。
要求キュー
キューを有効にすると、要求がレート制限を超えると、許可が使用可能になるまで、またはタイムアウトが発生するまで要求が待機するキューに配置されます。 要求は、構成可能なキューの順序に従って処理されます。
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 の場合、ページ ハンドラーではなく Page Razor に属性を適用する必要があります。 たとえば、[EnableRateLimiting]
は、OnGet
、OnPost
、または他のページ ハンドラーには適用できません。
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) Test Script Recorder などのツールを使用して JMeter スクリプトを作成し、スクリプトを Azure Load Testing に読み込みます。
ユーザー入力を使用してパーティションを作成すると、アプリは サービス拒否 (DoS) 攻撃に対して脆弱になります。 たとえば、クライアント IP アドレスにパーティションを作成すると、アプリは IP ソース アドレス スプーフィングを使用するサービス拒否攻撃に対して脆弱になります。 詳細については、『BCP 38 RFC 2827 ネットワーク イングレス フィルタリング: IP ソース アドレス スプーフィングを使用するサービス拒否攻撃の無効化』を参照してください。
その他のリソース
- Maarten Balliauw による「レート制限ミドルウェア」は、レート制限の優れた紹介と概要を提供します。
- .NET での HTTP ハンドラーのレート制限
ASP.NET Core