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 같은 호스팅 솔루션의 일부로 사용할 수 있습니다. 전용 보호는 WAF(웹 애플리케이션 방화벽) 또는 Cloudflare 또는 Akamai Kona Site Defender와 같은 CDN 솔루션의 일부로 사용할 수
속도 제한과 함께 상용 DDoS 보호 서비스를 구현하면 포괄적인 방어 전략을 제공하여 앱의 안정성, 보안 및 성능을 보장할 수 있습니다.
속도 제한 미들웨어 사용
다음 단계에서는 ASP.NET Core 앱에서 속도 제한 미들웨어를 사용하는 방법을 보여줍니다.
- 속도 제한 서비스를 구성합니다.
Program.cs
파일에서 적절한 속도 제한 정책을 추가하여 속도 제한 서비스를 구성합니다. 정책은 전역 또는 명명된 정책으로 정의할 수 있습니다. 다음 예제에서는 사용자(ID) 또는 전역적으로 분당 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
가 인 경우 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
호출할 수 있습니다.
슬라이딩 창 제한기
슬라이딩 창 알고리즘:
- 고정 창 제한기와 비슷하지만 창당 세그먼트를 추가합니다. 창은 각 세그먼트 간격마다 세그먼트 하나를 슬라이드합니다. 세그먼트 간격은 (창 시간)/(창당 세그먼트)입니다.
- 창에 대한 요청을
permitLimit
회로 제한합니다. - 각 시간 창은 창당
n
세그먼트로 나뉩니다. - 만료된 시간 세그먼트에서 가져온 요청은 이전 창의 세그먼트(
n
현재 세그먼트 이전의 세그먼트)로부터 현재 세그먼트에 추가됩니다. 창 한 개 전의 가장 만료된 시간 세그먼트를 만료된 세그먼트라고 합니다.
30초 창, 창당 3개의 세그먼트 및 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 |
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씩 증가합니다. 지정된 기간 동안 총 요청 수를 제한하는 다른 요청 제한기와 달리 동시성 제한기는 동시 요청 수만 제한하고 지정된 기간의 요청 수를 제한하지 않습니다.
다음 코드는 동시성 제한기를 사용합니다.
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 페이지에 적용할 수 있습니다.
Razor Pages의 경우, 특성은 페이지 처리기에 적용하는 것이 아니라 Razor 페이지에 적용해야 합니다. 예를 들어 [EnableRateLimiting]
를 OnGet
, OnPost
또는 다른 페이지 처리기에 적용할 수 없습니다.
[DisableRateLimiting]
특성은 명명된 속도 제한기 또는 전역 제한기 적용과 관계없이 컨트롤러, 작업 메서드 또는 Page에 대한 속도 제한을 사용하지 않도록 설정합니다.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 });
}
}
앞의 코드에서
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 Load Testing에 스크립트를 로드하세요.
사용자 입력을 사용하여 파티션을 만들면 앱이 DoS(서비스 거부) 공격에 취약해집니다. 예를 들어 클라이언트 IP 주소에 파티션을 만들면 앱이 IP 원본 주소 스푸핑을 사용하는 서비스 거부 공격에 취약해집니다. 자세한 내용은 BCP 38 RFC 2827 네트워크 수신 필터링: IP 원본 주소 스푸핑을 사용하는 서비스 거부 공격을 참조하세요.
추가 리소스
- Maarten Balliauw의 속도 제한 미들웨어 는 속도 제한에 대한 훌륭한 소개 및 개요를 제공합니다.
- .NET에서 HTTP 처리기 속도 제한
ASP.NET Core