基於客戶端 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 速率限制儲存在資料庫中,並在每個應用程式啟動後將其推送到快取中。