基于客户端 IP 的速率限制

使用 IpRateLimit 中间件,你可以为不同的场景设置多个限制,例如允许 IP 或 IP 范围在每秒,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<IpRateLimitOptions>(Configuration.GetSection("IpRateLimiting"));

    //load ip rules from appsettings.json
    services.Configure<IpRateLimitPolicies>(Configuration.GetSection("IpRateLimitPolicies"));

    // inject counter and rules stores
    services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();
    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.UseIpRateLimiting();

    app.UseMvc();
}

你应该在除 loggerFactory 之外的任何其他组件之前注册中间件。

如果你对应用程序进行负载平衡,则需要将 IDistributedCache 与 Redis 或 SQLServer 一起使用,以便所有的 kestrel 实例都具有相同的速率限制存储。你应该像这样注入分布式存储,而不是在内存存储中:

    // inject counter and rules distributed cache stores
    services.AddSingleton<IIpPolicyStore, DistributedCacheIpPolicyStore>();
    services.AddSingleton<IRateLimitCounterStore,DistributedCacheRateLimitCounterStore>();

配置和一般规则 appsettings.json

  "IpRateLimiting": {
    "EnableEndpointRateLimiting": false,
    "StackBlockedRequests": false,
    "RealIpHeader": "X-Real-IP",
    "ClientIdHeader": "X-ClientId",
    "HttpStatusCode": 429,
    "IpWhitelist": [ "127.0.0.1", "::1/10", "192.168.0.0/24" ],
    "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

当你的 Kestrel 服务器位于反向代理后面时,RealIpHeader 用于提取客户端 IP,如果你的代理使用不同的头,则 X-Real-IP 使用此选项进行设置。

ClientIdHeader 用于提取白名单的客户端 ID,如果此标头中存在客户端 ID 并且与 ClientWhitelist 中指定的值匹配,则不应用速率限制。

覆盖特定 IP appsettings.json 的一般规则

 "IpRateLimitPolicies": {
    "IpRules": [
      {
        "Ip": "84.247.85.224",
        "Rules": [
          {
            "Endpoint": "*",
            "Period": "1s",
            "Limit": 10
          },
          {
            "Endpoint": "*",
            "Period": "15m",
            "Limit": 200
          }
        ]
      },
      {
        "Ip": "192.168.3.22/25",
        "Rules": [
          {
            "Endpoint": "*",
            "Period": "1s",
            "Limit": 5
          },
          {
            "Endpoint": "*",
            "Period": "15m",
            "Limit": 150
          },
          {
            "Endpoint": "*",
            "Period": "12h",
            "Limit": 500
          }
        ]
      }
    ]
  }

IP 字段支持 IP v4 和 v6 值,范围如“192.168.0.0/24”,“fe80 :: / 10”或“192.168.0.0-192.168.0.255”。

定义速率限制规则

规则由端点,期间和限制组成。

端点格式为 {HTTP_Verb}:{PATH},你可以使用 asterix 符号来定位任何 HTTP 谓词。

期间格式为 {INT}{PERIOD_TYPE},你可以使用以下期间类型之一:s, m, h, d

限制格式是 {LONG}

示例

将所有端点的速率限制为每秒 2 次呼叫:

{
 "Endpoint": "*",
 "Period": "1s",
 "Limit": 2
}

如果,在相同的 IP 中,在同一秒内,你将对 api/values 进行 3 次 GET 调用,则最后一次调用将被阻止。但是如果在同一秒内你也调用了 PUT api/values,那么请求将会通过,因为它是一个不同的端点。启用端点速率限制后,每个呼叫都将根据 {HTTP_Verb}{PATH} 进行速率限制。

使用任何 HTTP 动词限制呼叫,每 15 分钟拨打 24 到 5 个呼叫:

{
 "Endpoint": "*:/api/values",
 "Period": "15m",
 "Limit": 5
}

速率限制每小时 5 个调用:

{
 "Endpoint": "get:/api/values",
 "Period": "1h",
 "Limit": 5
}

如果,在相同的 IP 中,在一小时内,你将对 api/values 进行 6 次 GET 调用,则最后一次调用将被阻止。但是如果在同一个小时内你也调用了 GET api / values / 1,那么请求将会通过,因为它是一个不同的端点。

行为

当客户端进行 HTTP 调用时,IpRateLimitMiddleware 执行以下操作:

  • 从请求对象中提取 IP,客户端 ID,HTTP 谓词和 URL,如果要实现自己的提取逻辑,可以覆盖 IpRateLimitMiddleware.SetIdentity
  • 在白名单中搜索 IP,客户端 ID 和 URL,如果有匹配则不执行任何操作
  • 在 IP 规则中搜索匹配项,所有适用的规则按期间分组,对于每个期间使用最严格的规则
  • 在匹配的一般规则中搜索,如果匹配的一般规则具有 IP 规则中不存在的定义时间段,则也使用此一般规则
  • 对于每个匹配规则,速率限制计数器递增,如果计数器值大于规则限制,则请求被阻止

如果请求被阻止,则客户端会收到如下文本响应:

Status Code: 429
Retry-After: 58
Content: API calls quota exceeded! maximum admitted 2 per 1m.

你可以通过更改这些选项 HttpStatusCodeQuotaExceededMessage 来自定义响应,如果你想要实现自己的响应,则可以覆盖 IpRateLimitMiddleware.ReturnQuotaExceededResponseRetry-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 记录阻止请求,如果要实现自己的记录,可以覆盖 IpRateLimitMiddleware.LogBlockedRequest。当请求获得速率限制时,默认记录器会发出以下信息:

info: AspNetCoreRateLimit.IpRateLimitMiddleware[0]
      Request get:/api/values from IP 84.247.85.224 has been blocked, quota 2/1m exceeded by 3. Blocked by rule *:/api/value, TraceIdentifier 0HKTLISQQVV9D.

在运行时更新速率限制

在应用程序启动时,appsettings.json 中定义的 IP 速率限制规则由 MemoryCacheClientPolicyStoreDistributedCacheIpPolicyStore 加载到缓存中,具体取决于你使用的缓存提供程序类型。你可以访问控制器内的 Ip 策略存储并修改 IP 规则,如下所示:

public class IpRateLimitController : Controller
{
    private readonly IpRateLimitOptions _options;
    private readonly IIpPolicyStore _ipPolicyStore;

    public IpRateLimitController(IOptions<IpRateLimitOptions> optionsAccessor, IIpPolicyStore ipPolicyStore)
    {
        _options = optionsAccessor.Value;
        _ipPolicyStore = ipPolicyStore;
    }

    [HttpGet]
    public IpRateLimitPolicies Get()
    {
        return _ipPolicyStore.Get(_options.IpPolicyPrefix);
    }

    [HttpPost]
    public void Post()
    {
        var pol = _ipPolicyStore.Get(_options.IpPolicyPrefix);

        pol.IpRules.Add(new IpRateLimitPolicy
        {
            Ip = "8.8.4.4",
            Rules = new List<RateLimitRule>(new RateLimitRule[] {
                new RateLimitRule {
                    Endpoint = "*:/api/testupdate",
                    Limit = 100,
                    Period = "1d" }
            })
        });

        _ipPolicyStore.Set(_options.IpPolicyPrefix, pol);
    }
}

这样,你可以将 IP 速率限制存储在数据库中,并在每个应用程序启动后将其推送到缓存中。