基于客户端 ID 的速率限制
使用 ClientRateLimit 中间件,你可以为不同的方案设置多个限制,例如允许客户端在每秒,15 分钟等时间间隔内进行最大数量的呼叫。你可以定义这些限制以解决对 API 或你的所有请求可以限制每个 URL 路径或 HTTP 动词和路径的限制。
建立
NuGet 安装 :
Install-Package AspNetCoreRateLimit
Startup.cs 代码 :
public void ConfigureServices(IServiceCollection services)
{
// needed to load configuration from appsettings.json
services.AddOptions();
// needed to store rate limit counters and ip rules
services.AddMemoryCache();
//load general configuration from appsettings.json
services.Configure<ClientRateLimitOptions>(Configuration.GetSection("ClientRateLimiting"));
//load client rules from appsettings.json
services.Configure<ClientRateLimitPolicies>(Configuration.GetSection("ClientRateLimitPolicies"));
// inject counter and rules stores
services.AddSingleton<IClientPolicyStore, MemoryCacheClientPolicyStore>();
services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();
// Add framework services.
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseClientRateLimiting();
app.UseMvc();
}
你应该在除 loggerFactory 之外的任何其他组件之前注册中间件。
如果你对应用程序进行负载平衡,则需要将 IDistributedCache
与 Redis 或 SQLServer 一起使用,以便所有的 kestrel 实例都具有相同的速率限制存储。你应该像这样注入分布式存储,而不是在内存存储中:
// inject counter and rules distributed cache stores
services.AddSingleton<IClientPolicyStore, DistributedCacheClientPolicyStore>();
services.AddSingleton<IRateLimitCounterStore,DistributedCacheRateLimitCounterStore>();
配置和一般规则 appsettings.json :
"ClientRateLimiting": {
"EnableEndpointRateLimiting": false,
"StackBlockedRequests": false,
"ClientIdHeader": "X-ClientId",
"HttpStatusCode": 429,
"EndpointWhitelist": [ "get:/api/license", "*:/api/status" ],
"ClientWhitelist": [ "dev-id-1", "dev-id-2" ],
"GeneralRules": [
{
"Endpoint": "*",
"Period": "1s",
"Limit": 2
},
{
"Endpoint": "*",
"Period": "15m",
"Limit": 100
},
{
"Endpoint": "*",
"Period": "12h",
"Limit": 1000
},
{
"Endpoint": "*",
"Period": "7d",
"Limit": 10000
}
]
}
如果 EnableEndpointRateLimiting
设置为 false
,则限制将全局应用,并且仅适用作为端点*
的规则。例如,如果你设置每秒 5 次调用的限制,则对任何端点的任何 HTTP 调用都将计入该限制。
如果 EnableEndpointRateLimiting
设置为 true
,则限制将适用于 {HTTP_Verb}{PATH}
中的每个端点。例如,如果你为*:/api/values
设置每秒 5 个呼叫的限制,则客户端可以每秒呼叫 GET /api/values
5 次,但也可以呼叫 5 次 PUT /api/values
。
如果 StackBlockedRequests
设置为 false
,拒绝的呼叫不会添加到节流计数器。如果客户端每秒发出 3 个请求并且你设置了每秒一个呼叫的限制,则其他限制(如每分钟或每天计数器)将仅记录第一个呼叫,即未阻止的呼叫。如果你希望被拒绝的请求计入其他限制,则必须将 StackBlockedRequests
设置为 true
。
ClientIdHeader
用于提取客户端 ID,如果此标头中存在客户端 ID 并且与 ClientWhitelist 中指定的值匹配,则不应用速率限制。
覆盖特定客户端 appsettings.json 的一般规则 :
"ClientRateLimitPolicies": {
"ClientRules": [
{
"ClientId": "client-id-1",
"Rules": [
{
"Endpoint": "*",
"Period": "1s",
"Limit": 10
},
{
"Endpoint": "*",
"Period": "15m",
"Limit": 200
}
]
},
{
"Client": "client-id-2",
"Rules": [
{
"Endpoint": "*",
"Period": "1s",
"Limit": 5
},
{
"Endpoint": "*",
"Period": "15m",
"Limit": 150
},
{
"Endpoint": "*",
"Period": "12h",
"Limit": 500
}
]
}
]
}
定义速率限制规则
规则由端点,期间和限制组成。
端点格式为 {HTTP_Verb}:{PATH}
,你可以使用 asterix 符号来定位任何 HTTP 谓词。
期间格式为 {INT}{PERIOD_TYPE}
,你可以使用以下期间类型之一:s, m, h, d
。
限制格式为 {LONG}
。
示例 :
将所有端点的速率限制为每秒 2 次呼叫:
{
"Endpoint": "*",
"Period": "1s",
"Limit": 2
}
如果在同一秒内,客户端对 api /值进行 3 次 GET 调用,则最后一次调用将被阻止。但是如果在同一秒内他也调用了 PUT api /值,那么请求将会通过,因为它是一个不同的端点。当启用端点速率限制时,每个呼叫都根据 {HTTP_Verb}{PATH}
进行速率限制。
使用任何 HTTP 动词限制呼叫,每 15 分钟拨打 22 到 5 个呼叫:
{
"Endpoint": "*:/api/values",
"Period": "15m",
"Limit": 5
}
速率限制每小时 5 个调用:
{
"Endpoint": "get:/api/values",
"Period": "1h",
"Limit": 5
}
如果在一小时内,客户端对 api /值进行 6 次 GET 调用,则最后一次调用将被阻止。但是如果在同一个小时内他也调用了 GET api / values / 1,那么请求将会通过,因为它是一个不同的端点。
行为
当客户端进行 HTTP 调用时,ClientRateLimitMiddleware 执行以下操作:
- 从请求对象中提取客户端 ID,HTTP 谓词和 URL,如果要实现自己的提取逻辑,可以覆盖
ClientRateLimitMiddleware.SetIdentity
- 在白名单中搜索客户端 ID 和 URL,如果有匹配则不执行任何操作
- 在客户端规则中搜索匹配项,所有适用的规则按期间分组,对于每个期间使用最严格的规则
- 在匹配的常规规则中搜索,如果匹配的一般规则具有客户端规则中不存在的已定义时间段,则也会使用此一般规则
- 对于每个匹配规则,速率限制计数器递增,如果计数器值大于规则限制,则请求被阻止
如果请求被阻止,则客户端会收到如下文本响应:
Status Code: 429
Retry-After: 58
Content: API calls quota exceeded! maximum admitted 2 per 1m.
你可以通过更改这些选项 HttpStatusCode
和 QuotaExceededMessage
来自定义响应,如果你想要实现自己的响应,则可以覆盖 ClientRateLimitMiddleware.ReturnQuotaExceededResponse
。Retry-After
标头值以秒表示。
如果请求没有得到速率限制,那么匹配规则中定义的最长周期用于组成 X-Rate-Limit 标头,这些标头将在响应中注入:
X-Rate-Limit-Limit: the rate limit period (eg. 1m, 12h, 1d)
X-Rate-Limit-Remaining: number of request remaining
X-Rate-Limit-Reset: UTC date time when the limits resets
默认情况下,使用 Microsoft.Extensions.Logging.ILogger
记录阻止请求,如果要实现自己的记录,可以覆盖 ClientRateLimitMiddleware.LogBlockedRequest
。当请求获得速率限制时,默认记录器会发出以下信息:
info: AspNetCoreRateLimit.ClientRateLimitMiddleware[0]
Request get:/api/values from ClientId client-id-1 has been blocked, quota 2/1m exceeded by 3. Blocked by rule *:/api/value, TraceIdentifier 0HKTLISQQVV9D.
在运行时更新速率限制
在应用程序启动时,appsettings.json
中定义的客户端速率限制规则由 MemoryCacheClientPolicyStore
或 DistributedCacheClientPolicyStore
加载到缓存中,具体取决于你使用的缓存提供程序类型。你可以访问控制器内的客户端策略存储并修改规则,如下所示:
public class ClientRateLimitController : Controller
{
private readonly ClientRateLimitOptions _options;
private readonly IClientPolicyStore _clientPolicyStore;
public ClientRateLimitController(IOptions<ClientRateLimitOptions> optionsAccessor, IClientPolicyStore clientPolicyStore)
{
_options = optionsAccessor.Value;
_clientPolicyStore = clientPolicyStore;
}
[HttpGet]
public ClientRateLimitPolicy Get()
{
return _clientPolicyStore.Get($"{_options.ClientPolicyPrefix}_cl-key-1");
}
[HttpPost]
public void Post()
{
var id = $"{_options.ClientPolicyPrefix}_cl-key-1";
var clPolicy = _clientPolicyStore.Get(id);
clPolicy.Rules.Add(new RateLimitRule
{
Endpoint = "*/api/testpolicyupdate",
Period = "1h",
Limit = 100
});
_clientPolicyStore.Set(id, clPolicy);
}
}
这样,你可以将客户端速率限制存储在数据库中,并在每个应用程序启动后将其推送到缓存中。