AspNetCore 限流中间件IpRateLimitMiddleware 介绍

IpRateLimitMiddleware(Github: AspNetCoreRateLimit) 是ASPNETCore的一个限流的中间件,用于控制客户端调用API的频次, 如果客户端频繁访问服务器,可以限制它的频率,已降低访问服务器端的压力。或者如果有爬虫在爬取关键数据,也可以限制某个/某些API或者某些IP的每天调取次数, 这样限制他爬取的速度。

当然, 其实我要解决的是另外一个问题。 我们写的WebApi有时候会存在一些API,我们只希望其它内部应用来调用,比如,WebApi的HealthCheck, 我们就希望只有我们的中台可以定时调用来获取信息, 而前端是不能调用。这个我们就可以把内部的IP地址放到IpWhitelist配置项中, 并且限制特定的API调用次数为0次, 这样只有白名单里面的地址可以访问对应的端点, 如下所示。

"GeneralRules": [
      {
        "Endpoint": "get:/api/healthstatus",
        "Period": "1s",
        "Limit": 0
      }]

IPRateLimitMiddleWare详细使用说明,翻译来自于Github(AspNetCoreRateLimit)

建立

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

        // https://github.com/aspnet/Hosting/issues/793
        // the IHttpContextAccessor service is not registered by default.
        // the clientId/clientIp resolvers use it.
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

        // configuration (resolvers, counter key builders)
        services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseIpRateLimiting();

    app.UseMvc();
}

IPRateLimiting应该在其他中间件之前注册, 否则API请求计算可能不正确。

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

// 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,拒绝的API调用不会添加到调用次数计数器上。比如: 如果客户端每秒发出3个请求并且您设置了每秒一个调用的限制,则每分钟或每天计数器等其他限制将仅记录第一个调用,即成功的API调用。如果您希望被拒绝的API调用计入其他时间的显示(分钟,小时等),则必须设置StackBlockedRequeststrue

RealIpHeader使用时,你的Kestrel 服务器背后是一个反向代理,如果你的代理服务器使用不同的页眉然后提取客户端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"。

如果您在appsettings.json配置文件中定义了静态速率策略,则需要在应用程序启动时为它们设定种子:

public static async Task Main(string[] args)
{
    IWebHost webHost = CreateWebHostBuilder(args).Build();

    using (var scope = webHost.Services.CreateScope())
    {
         // get the IpPolicyStore instance
         var ipPolicyStore = scope.ServiceProvider.GetRequiredService<IIpPolicyStore>();

         // seed IP data from appsettings
         await ipPolicyStore.SeedAsync();
    }

    await webHost.RunAsync();
}

定义速率限制规则

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

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

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

限制格式是{LONG}

示例:

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

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

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

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

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

速率限制GET呼叫/api/values每小时5次呼叫:

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

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

行为

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

  • 从请求对象中提取IP,客户端ID,HTTP谓词和URL,如果要实现自己的提取逻辑,可以覆盖该IpRateLimitMiddleware.ResolveIdentity方法或实现自定义IP /客户端解析器:
public class CustomRateLimitConfiguration : RateLimitConfiguration
{
    protected override void RegisterResolvers()
    {
        base.RegisterResolvers();

        ClientResolvers.Add(new ClientQueryStringResolveContributor(HttpContextAccessor, ClientRateLimitOptions.ClientIdHeader));
    }
}
  • 在白名单中搜索IP,客户端ID和URL,如果匹配任何内容,则不执行任何操作。

  • 在IP规则中搜索匹配项,所有适用的规则按期间分组,对于每个期间使用的限制性最强的规则。

  • 在匹配的一般规则中搜索,如果匹配的一般规则具有IP规则中不存在的定义时段,则也使用此一般规则。

  • 对于每个匹配规则,速率限制计数器递增,如果计数器值大于规则限制,则请求被阻止。

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

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

您可以自定义通过改变这些选项的反应HttpStatusCodeQuotaExceededMessage,如果你想实现自己的回应,你可以重写IpRateLimitMiddleware.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 (ISO 8601) when the limits resets

客户端可以解析X-Rate-Limit-Reset这样的:

DateTime resetDate = DateTime.ParseExact(resetHeader, "o", DateTimeFormatInfo.InvariantInfo);

可以通过在appsettings.json中设置DisableRateLimitHeaders选项来禁用X-Rate-Limit和Retry-After标头true

默认情况下,使用阻止请求进行记录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.

在运行时更新速率限制

在应用程序启动时,定义的IP速率限制规则将appsettings.json通过任一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速率限制存储在数据库中,并在每个应用程序启动后将其推送到缓存中。

    12人点赞   ASPNET

 

 
上一篇:B样条数据点反求控制点绘制曲线(源码)


下一篇:Azure Firewall 配合Endpoint玩转Azure SQL