如何在ASP.NET Core中限制请求速率

什么是速率限制

速率限制是保护Web应用程序和API免受恶意攻击和过度使用的一项关键技术。通过限制特定时间窗口内允许的请求数量,速率限制有助于防止分布式拒绝服务(DDoS)攻击和 API滥用。

中间件Microsoft.AspNetCore.RateLimiting提供限速中间件

速率限制算法

固定窗口算法(Fixed Window Algorithm)

固定窗口算法允许特定时间窗口内固定数量的请求。任何超出限制的后续请求都会受到限制。
代码示例

1
2
3
4
5
6
7
8
9
builder.Services.AddRateLimiter(option =>
{
option.RejectionStatusCode = 429;
option.AddFixedWindowLimiter(policyName: "fixed", option =>
{
option.PermitLimit = 3;
option.Window = TimeSpan.FromMinutes(1);
});
});
  • PermitLimit表示在指定的时间窗口内允许的最大请求数
  • Window表示在指定的时间窗口内
滑动窗口算法(Sliding Window Algorithm)

滑动窗口算法与固定窗口算法类似,滑动窗口算法是将时间窗口分割成段
代码示例

1
2
3
4
5
6
7
8
9
10
builder.Services.AddRateLimiter(option =>
{
option.RejectionStatusCode = 429;
option.AddSlidingWindowLimiter(policyName: "sliding", option =>
{
option.PermitLimit = 3;
option.Window = TimeSpan.FromMinutes(1);
option.SegmentsPerWindow = 1;
});
});
  • Window: 定义了滑动窗口的持续时间,在此时间范围内请求将被计数并受到限制。
  • SegmentsPerWindow:指定滑动窗口被分成的段数,它被设置为1,意味着滑动窗口不会被进一步划分为更小的段。

在使用滑动窗口算法进行速率限制的上下文中,“分段”是指将时间窗口划分为更小的间隔。每个片段代表整个时间窗口的一部分。
使用滑动窗口速率限制器时,时间窗口被分为多个段来跟踪和强制执行速率限制。段的数量决定了窗口内速率限制的粒度。
例如,如果您的时间窗口为 1 分钟并指定 2 段,则意味着时间窗口被分为两个相等的间隔,每个间隔为 30 秒。速率限制在每个段内单独应用,允许每个段一定数量的请求。

令牌桶算法(Token Bucket Algorithm)

令牌桶算法为每个请求分配令牌,并且仅当令牌可用时才允许处理,令牌以固定速率补充
代码示例

1
2
3
4
5
6
7
8
9
10
builder.Services.AddRateLimiter(option =>
{
option.RejectionStatusCode = 429;
option.AddTokenBucketLimiter(policyName: "tokenbucket", option =>
{
option.TokenLimit = 3;
option.TokensPerPeriod = 5;
option.ReplenishmentPeriod = TimeSpan.FromMinutes(1);
});
});
  • TokenLimit:在任何给定时间可用的最大令牌(请求)数
  • TokensPerPeriod:每个周期生成的令牌数量
  • ReplenishmentPeriod:设置令牌生成的间隔时间,它决定重新生成令牌的频率

令牌桶算法通过为每个传入请求分配一个令牌来工作。如果令牌桶中有令牌可用,则允许请求并消耗令牌。如果令牌桶为空,表明已达到速率限制,请求将被拒绝或被限制。
通过以上配置,令牌桶限制器在1分钟内生成5个令牌但最多允许3次请求(令牌)。一旦达到令牌限制,任何其他请求都将受到速率限制。

并发算法(Concurrency Algorithm)

并发速率限制算法限制的是请求的并发执行数,而不是一个时间窗口内的请求总数。
示例代码

1
2
3
4
5
6
7
8
9
builder.Services.AddRateLimiter(option =>
{
option.RejectionStatusCode = 429;
option.AddConcurrencyLimiter(policyName: "concurrency", option =>
{
option.PermitLimit = 3;
option.QueueLimit = 3;
});
});
  • PermitLimit表示允许同时(同一时间)请求的最大数量
  • QueueLimit设置可以排队的最大请求数量

通过上述配置,并发限制器最多允许3个并发请求,如果达到限制,则最多可以对3个额外请求进行排队。任何超出许可和队列限制的请求都将受到速率限制。

实际运用
  1. 配置速率限制策略(参考上述速率限制算法)
  2. 启用速率限制中间件
1
app.UseRateLimiter();
  1. 打开要应用速率限制的控制器文件,找到控制器类声明,在类声明上方添加属性 [EnableRateLimiting(“PolicyName”)] (”PolicyName”替换为要应用的速率限制策略的实际名称)

示例代码

1
2
3
4
5
6
7
8
[EnableRateLimiting("tokenbucket")]
public class HomeController : Controller
{
public IActionResult Index()
{
return Ok("Hello world");
}
}

在上面的示例中,EnableRateLimiting属性应用于HomeController类,指定”tokenbucket”速率限制策略。

什么是AspNetCoreRateLimit

AspNetCoreRateLimit 是一个流行的开源库,用于在ASP.NET Core 应用程序中进行速率限制。该库允许您根据客户端IP地址、路由和HTTP方法定义速率限制规则,从而使您能够对应用程序的速率限制策略进行细粒度控制。

安装命令

1
Install-Package AspNetCoreRateLimit

配置

  • 打开appsettings.json
  • 添加以下配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"IpRateLimiting": {
"EnableEndpointRateLimiting": true,
"StackBlockedRequests": false,
"RealIPHeader": "X-Real-IP",
"ClientIdHeader": "X-ClientId",
"HttpStatusCode": 429,
"GeneralRules": [
{
"Endpoint": "*",
"Period": "1m",
"Limit": 3,
"QuotaExceededMessage": "Too many requests."
}
]
}

参数解析

  • EnableEndpointRateLimiting:指定是否启用基于端点的速率限制
  • StackBlockedRequests:决定是否将阻塞的请求堆叠起来供以后处理
  • RealIPHeader:包含客户端真实IP地址的标头名称
  • ClientIdHeader:包含客户端唯一标识符的标头名称
  • HttpStatusCode:超出速率限制时返回的 HTTP 状态代码
  • GeneralRules: 一组速率限制规则。

在此示例中方便测试,我们有一个适用于所有端点的规则 (*)。它将请求限制为每分钟3个,并提供超出配额时返回的消息。

实际运用

  1. 打开Program文件,添加以下代码
1
2
3
4
5
6
7
8
9
10
11
var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<IpRateLimitOptions>(builder.Configuration.GetSection("IpRateLimiting"));
builder.Services.AddMemoryCache();
builder.Services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();
builder.Services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();
builder.Services.AddSingleton<IRateLimitConfiguration,RateLimitConfiguration>();
builder.Services.AddSingleton<ProcessingStrategy,AsyncKeyLockProcessingStrategy>();
builder.Services.AddInMemoryRateLimiting();

var app = builder.Build();
app.UseIpRateLimiting();

上述代码实现了添加内存缓存,从appsettings.json读取IpRateLimitOptions配置并注册速率限制服务。把UseIpRateLimiting中间件添加到管道中,该中间件将处理我们端点的速率限制执行。

测试速率限制

假设我们有一个接口/api/customers要对其进行速率限制,如果超过每分钟 3个请求的配置限制,接口应该返回响应429 Too Many Requests。
尝试在一分钟内向接口发送多个请求/api/customers,正常结果应该会收到超出速率限制的响应
测试结果

正常结果
正常结果

限速结果
限速结果

限制每个接口的请求

在前面的示例中,我们使用通配符将速率限制规则应用于所有端点”*”。但是,我们还可以为各个端点定义特定的速率限制。要将速率限制应用于特定端点,修改appsettings.json文件中”GeneralRules”中的数组,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"GeneralRules": [
{
"Endpoint": "/api/customers",
"Period": "1m",
"Limit": 30,
"QuotaExceededMessage": "Too many requests for /api/customers."
},
{
"Endpoint": "/api/other",
"Period": "1h",
"Limit": 100,
"QuotaExceededMessage": "Too many requests for /api/other."
}
]