基於客戶端 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.

你可以通過更改這些選項 HttpStatusCodeQuotaExceededMessage 來自定義響應,如果你想要實現自己的響應,則可以覆蓋 ClientRateLimitMiddleware.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 記錄阻止請求,如果要實現自己的記錄,可以覆蓋 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 中定義的客戶端速率限制規則由 MemoryCacheClientPolicyStoreDistributedCacheClientPolicyStore 載入到快取中,具體取決於你使用的快取提供程式型別。你可以訪問控制器內的客戶端策略儲存並修改規則,如下所示:

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);
    }
}

這樣,你可以將客戶端速率限制儲存在資料庫中,並在每個應用程式啟動後將其推送到快取中。