ASP.NET Core 中的速率限制中间件

作者:Arvin KahbaziMaarten BalliauwRick Anderson

Microsoft.AspNetCore.RateLimiting 中间件提供速率限制中间件。 应用可配置速率限制策略,然后将策略附加到终结点。 对于采用速率限制的应用,在部署前应仔细进行负载测试和评审。 有关详细信息,请参阅本文中的测试具有速率限制的终结点

有关速率限制的简介,请参阅速率限制中间件

为何使用速率限制

速率限制可用于管理向应用发出的传入请求流。 实现速率限制的关键原因:

  • 防止滥用:速率限制通过限制用户或客户端在给定时间段内发出的请求数来帮助保护应用免受滥用。 这对于公共 API 尤其重要。
  • 确保公平使用:通过设置限制,所有用户都可以公平地访问资源,防止用户垄断系统。
  • 保护资源:速率限制通过控制可处理的请求数来帮助防止服务器重载,从而防止后端资源过载。
  • 增强安全性:它可以通过限制处理请求的速度来缓解拒绝服务(DoS)攻击的风险,从而使攻击者更难淹没系统。
  • 提高性能:通过控制传入请求的速度,可以维护应用的最佳性能和响应能力,确保更好的用户体验。
  • 成本管理:对于基于使用情况产生成本的服务,速率限制可以通过控制处理的请求量来帮助管理和预测费用。

在 ASP.NET Core 应用中实现速率限制有助于维护稳定性、安全性和性能,确保所有用户获得可靠高效的服务。

防止 DDoS 攻击

虽然速率限制通过限制处理请求的速率来帮助缓解拒绝服务(DoS)攻击的风险,但它不是分布式拒绝服务(DDoS)攻击的综合解决方案。 DDoS 攻击涉及多个系统,使应用遭受大量请求,因此难以单独处理速率限制。

对于可靠的 DDoS 保护,请考虑使用商业 DDoS 保护服务。 这些服务提供高级功能,例如:

  • 流量分析:持续监视和分析传入流量,实时检测和缓解 DDoS 攻击。
  • 可伸缩性:通过跨多个服务器和数据中心分布流量来处理大规模攻击的能力。
  • 自动缓解:自动响应机制,无需手动干预即可快速阻止恶意流量。
  • 全球网络:一个全局服务器网络,用于吸收和缓解离源更近的攻击。
  • 不断更新:商业服务持续跟踪和更新其保护机制,以适应新的和不断演变的威胁。

使用云托管服务时,DDoS 防护通常作为托管解决方案的一部分提供,例如 Azure Web 应用程序防火墙AWS 防护Google Cloud Armor。 专用保护可用作 Web 应用程序防火墙(WAF)或 CDN 解决方案的一部分,例如 CloudflareAkamai Kona Site Defender

结合速率限制实施商业 DDoS 保护服务可以提供全面的防御策略,确保应用的稳定性、安全性和性能。

使用速率限制中间件

以下步骤演示如何在 ASP.NET Core 应用中使用速率限制中间件:

  1. 配置速率限制服务。

Program.cs 文件中,通过添加适当的速率限制策略来配置速率限制服务。 策略可以定义为全局策略或命名策略。 以下示例按用户(标识)或全局允许每分钟 10 个请求:

builder.Services.AddRateLimiter(options =>
{
    options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
        RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: httpContext.User.Identity?.Name ?? httpContext.Request.Headers.Host.ToString(),
            factory: partition => new FixedWindowRateLimiterOptions
            {
                AutoReplenishment = true,
                PermitLimit = 10,
                QueueLimit = 0,
                Window = TimeSpan.FromMinutes(1)
            }));
});

命名策略需要显式应用于页面或终结点。 以下示例会添加一个名为 "fixed" 的固定窗口限制程序策略,而后续我们会将其添加到终结点:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("fixed", opt =>
    {
        opt.PermitLimit = 4;
        opt.Window = TimeSpan.FromSeconds(12);
        opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        opt.QueueLimit = 2;
    });
});

var app = builder.Build();

全局限制程序会在通过 options.GlobalLimiter 进行配置时自动应用于所有终结点。

  1. 启用速率限制中间件

    Program.cs 文件中,通过调用 UseRateLimiter 启用速率限制中间件:

app.UseRouting();

app.UseRateLimiter();

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});

app.Run();

将速率限制策略应用于终结点或页面

将速率限制应用于 WebAPI 终结点

将命名策略应用于终结点或组,例如:


app.MapGet("/api/resource", () => "This endpoint is rate limited")
   .RequireRateLimiting("fixed"); // Apply specific policy to an endpoint

将速率限制应用于 MVC 控制器

将配置的速率限制策略应用于特定终结点或全局。 例如,将“固定”策略应用于所有控制器终结点:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers().RequireRateLimiting("fixed");
});

将速率限制应用于服务器端 Blazor 应用

若要为所有应用的可路由 Razor 组件设置速率限制,请在 Program 文件中使用 MapRazorComponents 调用的速率限制策略名称指定 RequireRateLimiting。 在以下示例中,将应用名为“policy”“的速率限制策略:

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode()
    .RequireRateLimiting("policy");

若要通过 _Imports.razor 文件为单个可路由 Razor 组件或组件文件夹设置策略,请将[EnableRateLimiting]属性与策略名称一起应用。 在以下示例中,将应用名为“override”“的速率限制策略。 该策略替换当前应用于终结点的任何策略。 全局限制程序仍在应用此属性的终结点上运行。

@page "/counter"
@using Microsoft.AspNetCore.RateLimiting
@attribute [EnableRateLimiting("override")]

<h1>Counter</h1>

如果 RequireRateLimitingMapRazorComponents 上调用,则[EnableRateLimiting]属性仅通过 _Imports.razor 文件应用于可路由组件或组件文件夹。

[DisableRateLimiting] 属性 用于通过 _Imports.razor 文件禁用可路由组件或组件文件夹的速率限制。

速率限制器算法

RateLimiterOptionsExtensions 类提供下列用于限制速率的扩展方法:

固定限制器、滑动限制器和令牌限制器都限制一段时间内的最大请求数。 并发限制器仅限制并发请求数,不对一段时间内的请求数设置上限。 选择限制器时,应考虑终结点的成本。 终结点的成本包括使用的资源(例如时间、数据访问、CPU 和 I/O)的费用。

固定窗口限制器

AddFixedWindowLimiter 方法使用固定的时间窗口来限制请求。 当时间窗口过期时,会启动一个新的时间窗口,并重置请求限制。

考虑下列代码:

using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRateLimiter(_ => _
    .AddFixedWindowLimiter(policyName: "fixed", options =>
    {
        options.PermitLimit = 4;
        options.Window = TimeSpan.FromSeconds(12);
        options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        options.QueueLimit = 2;
    }));

var app = builder.Build();

app.UseRateLimiter();

static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");

app.MapGet("/", () => Results.Ok($"Hello {GetTicks()}"))
                           .RequireRateLimiting("fixed");

app.Run();

前面的代码:

  • 调用 AddRateLimiter 来将速率限制服务添加到服务集合。
  • 调用 AddFixedWindowLimiter 来创建策略名为 "fixed" 的固定窗口限制器,并进行如下设置:
  • PermitLimit 设置为 4,将时间 Window 设置为 12。 允许每 12 秒的窗口最多 4 个请求。
  • QueueProcessingOrderOldestFirst
  • QueueLimit 到 2(将此设置为 0 以禁用排队机制)。
  • 调用 UseRateLimiter 来启用速率限制。

应用应使用配置来设置限制器选项。 以下代码使用 MyRateLimitOptions 更新上述代码来进行配置:

using System.Threading.RateLimiting;
using Microsoft.AspNetCore.RateLimiting;
using WebRateLimitAuth.Models;

var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<MyRateLimitOptions>(
    builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit));

var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
var fixedPolicy = "fixed";

builder.Services.AddRateLimiter(_ => _
    .AddFixedWindowLimiter(policyName: fixedPolicy, options =>
    {
        options.PermitLimit = myOptions.PermitLimit;
        options.Window = TimeSpan.FromSeconds(myOptions.Window);
        options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        options.QueueLimit = myOptions.QueueLimit;
    }));

var app = builder.Build();

app.UseRateLimiter();

static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");

app.MapGet("/", () => Results.Ok($"Fixed Window Limiter {GetTicks()}"))
                           .RequireRateLimiting(fixedPolicy);

app.Run();

在使用速率限制终结点特定的 API 时,必须在 UseRateLimiter 之后调用 UseRouting。 例如,如果使用 [EnableRateLimiting] 属性,则必须在 UseRateLimiter 之后调用 UseRouting。 当仅调用全局限制器时,可以在 UseRateLimiter 之前调用 UseRouting

滑动窗口限制器

滑动窗口算法:

  • 与固定窗口限制器类似,但为每个窗口添加了段。 窗口在每个段间隔滑动一段。 段间隔的计算方式是:(窗口时间)/(每个窗口的段数)。
  • 将窗口的请求数限制为 permitLimit 个请求。
  • 每个时间窗口划分为一个窗口 n 个段。
  • 从倒退一个窗口的过期时间段(当前段之前的 n 个段)获取的请求会添加到当前的段。 我们将倒退一个窗口最近过期时间段称为“过期的段”。

请考虑下表,其中显示了一个滑动窗口限制器,该限制器的窗口为 30 秒、每个窗口有三个段,且请求数限制为 100 个:

  • 第一行和第一列显示时间段。
  • 第二行显示剩余的可用请求数。 其余请求数的计算方式为可用请求数减去处理的请求数和回收的请求数。
  • 每次的请求数沿着蓝色对角线移动。
  • 从时间 30 开始,从过期时间段获得的请求会再次添加到请求数限制中,如红色线条所示。

显示请求数、限制和回收的槽数的表

下表换了一种格式来显示上图中的数据。 “可用”列显示上一个段中可用的请求数(来自上一个行中的“结转”)。 第一行显示有 100 个可用请求,因为没有上一个段。

时间 可用 获取的请求数 从过期段回收的请求数 结存请求数
0 100 20 0 80
10 80 30 0 50
20 50 40 0 10
30 10 30 20 0
40 0 10 30 20
50 20 10 40 50
六十 50 35 30 45

以下代码使用滑动窗口速率限制器:

using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;

var builder = WebApplication.CreateBuilder(args);

var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
var slidingPolicy = "sliding";

builder.Services.AddRateLimiter(_ => _
    .AddSlidingWindowLimiter(policyName: slidingPolicy, options =>
    {
        options.PermitLimit = myOptions.PermitLimit;
        options.Window = TimeSpan.FromSeconds(myOptions.Window);
        options.SegmentsPerWindow = myOptions.SegmentsPerWindow;
        options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        options.QueueLimit = myOptions.QueueLimit;
    }));

var app = builder.Build();

app.UseRateLimiter();

static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");

app.MapGet("/", () => Results.Ok($"Sliding Window Limiter {GetTicks()}"))
                           .RequireRateLimiting(slidingPolicy);

app.Run();

令牌桶限制器

令牌桶限流器与滑动窗口限制器类似,但是它并不重新添加来自过期段的请求,而是在每个补充周期内一次性补充固定数量的令牌。 每个段添加的令牌数不能使可用令牌数超过令牌桶限制。 下表显示了一个令牌桶限制器,其中令牌数限制为 100 个,补充期为 10 秒。

时间 可用 获取的请求数 已添加 结存请求数
0 100 20 0 80
10 80 10 20 90
20 90 5 15 100
30 100 30 20 90
40 90 6 16 100
50 100 40 20 80
六十 80 50 20 50

以下代码使用令牌桶限制器:

using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;

var builder = WebApplication.CreateBuilder(args);

var tokenPolicy = "token";
var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);

builder.Services.AddRateLimiter(_ => _
    .AddTokenBucketLimiter(policyName: tokenPolicy, options =>
    {
        options.TokenLimit = myOptions.TokenLimit;
        options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        options.QueueLimit = myOptions.QueueLimit;
        options.ReplenishmentPeriod = TimeSpan.FromSeconds(myOptions.ReplenishmentPeriod);
        options.TokensPerPeriod = myOptions.TokensPerPeriod;
        options.AutoReplenishment = myOptions.AutoReplenishment;
    }));

var app = builder.Build();

app.UseRateLimiter();

static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");

app.MapGet("/", () => Results.Ok($"Token Limiter {GetTicks()}"))
                           .RequireRateLimiting(tokenPolicy);

app.Run();

AutoReplenishment 设置为 true 时,内部计时器每 ReplenishmentPeriod 时间补充一次令牌数;当 false 设置为 TryReplenish 时,应用必须对限制器调用 。

并发限制器

并发限制器会限制并发请求数。 每添加一个请求,在并发限制中减去 1。 一个请求完成时,在限制中增加 1。 其他请求限制器限制的是指定时间段的请求总数,而与它们不同,并发限制器仅限制并发请求数,不对一段时间内的请求数设置上限。

以下代码使用并发限制器:

using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;

var builder = WebApplication.CreateBuilder(args);

var concurrencyPolicy = "Concurrency";
var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);

builder.Services.AddRateLimiter(_ => _
    .AddConcurrencyLimiter(policyName: concurrencyPolicy, options =>
    {
        options.PermitLimit = myOptions.PermitLimit;
        options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        options.QueueLimit = myOptions.QueueLimit;
    }));

var app = builder.Build();

app.UseRateLimiter();

static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");

app.MapGet("/", async () =>
{
    await Task.Delay(500);
    return Results.Ok($"Concurrency Limiter {GetTicks()}");
                              
}).RequireRateLimiting(concurrencyPolicy);

app.Run();

速率限制分区

速率限制分区将流量划分为单独的“存储桶”,每个分区都获得自己的速率限制计数器。 这允许比单个全局计数器更精细的控制。 分区“存储桶”由不同的键(如用户 ID、IP 地址或 API 密钥)定义。

分区的优点

  • 公平性:一个用户不能耗尽所有人的速率限制
  • 粒度:不同用户/资源的不同限制
  • 安全性:更好地防范有针对性的滥用
  • 分层服务:支持具有不同限制的服务层

通过分区速率限制,可以精细控制管理 API 流量的方式,同时确保资源分配公平。

按 IP 地址

options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
    RateLimitPartition.GetFixedWindowLimiter(
        partitionKey: httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown",
        factory: _ => new FixedWindowRateLimiterOptions
        {
            PermitLimit = 50,
            Window = TimeSpan.FromMinutes(1)
        }));

按用户 Identity

options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
    RateLimitPartition.GetFixedWindowLimiter(
        partitionKey: httpContext.User.Identity?.Name ?? "anonymous",
        factory: _ => new FixedWindowRateLimiterOptions
        {
            PermitLimit = 100,
            Window = TimeSpan.FromMinutes(1)
        }));

按 API 键

options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
{
    string apiKey = httpContext.Request.Headers["X-API-Key"].ToString() ?? "no-key";

    // Different limits based on key tier
    return apiKey switch
    {
        "premium-key" => RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: apiKey,
            factory: _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 1000,
                Window = TimeSpan.FromMinutes(1)
            }),

        _ => RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: apiKey,
            factory: _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 100,
                Window = TimeSpan.FromMinutes(1)
            }),
    };
});

按终结点路径

options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
{
    string path = httpContext.Request.Path.ToString();

    // Different limits for different paths
    if (path.StartsWith("/api/public"))
    {
        return RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: $"{httpContext.Connection.RemoteIpAddress}-public",
            factory: _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 30,
                Window = TimeSpan.FromSeconds(10)
            });
    }

    return RateLimitPartition.GetFixedWindowLimiter(
        partitionKey: httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown",
        factory: _ => new FixedWindowRateLimiterOptions
        {
            PermitLimit = 100,
            Window = TimeSpan.FromMinutes(1)
        });
});

创建链式限制器

通过 CreateChained API 可传入多个 PartitionedRateLimiter,这些 PartitionedRateLimiter 被组合成一个 。 组合的限制器按顺序运行所有输入限制器。

以下代码使用 CreateChained

using System.Globalization;
using System.Threading.RateLimiting;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRateLimiter(_ =>
{
    _.OnRejected = async (context, cancellationToken) =>
    {
        if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
        {
            context.HttpContext.Response.Headers.RetryAfter =
                ((int) retryAfter.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo);
        }

        context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
        await context.HttpContext.Response.WriteAsync("Too many requests. Please try again later.", cancellationToken);
    };
    _.GlobalLimiter = PartitionedRateLimiter.CreateChained(
        PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
        {
            var userAgent = httpContext.Request.Headers.UserAgent.ToString();

            return RateLimitPartition.GetFixedWindowLimiter
            (userAgent, _ =>
                new FixedWindowRateLimiterOptions
                {
                    AutoReplenishment = true,
                    PermitLimit = 4,
                    Window = TimeSpan.FromSeconds(2)
                });
        }),
        PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
        {
            var userAgent = httpContext.Request.Headers.UserAgent.ToString();
            
            return RateLimitPartition.GetFixedWindowLimiter
            (userAgent, _ =>
                new FixedWindowRateLimiterOptions
                {
                    AutoReplenishment = true,
                    PermitLimit = 20,    
                    Window = TimeSpan.FromSeconds(30)
                });
        }));
});

var app = builder.Build();
app.UseRateLimiter();

static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");

app.MapGet("/", () => Results.Ok($"Hello {GetTicks()}"));

app.Run();

有关详细信息,请参阅 CreateChained 源代码

选择当请求速率受限时的处理方式

对于简单情况,只需设置状态代码:

builder.Services.AddRateLimiter(options =>
{
    // Set a custom status code for rejections
    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;

    // Rate limiter configuration...
});

最常见的方法是在配置速率限制时注册 OnRejected 回调:

builder.Services.AddRateLimiter(options =>
{
    // Rate limiter configuration...

    options.OnRejected = async (context, cancellationToken) =>
    {
        // Custom rejection handling logic
        context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
        context.HttpContext.Response.Headers["Retry-After"] = "60";

        await context.HttpContext.Response.WriteAsync("Rate limit exceeded. Please try again later.", cancellationToken);

        // Optional logging
        logger.LogWarning("Rate limit exceeded for IP: {IpAddress}",
            context.HttpContext.Connection.RemoteIpAddress);
    };
});

另一个选项是对请求进行排队:

请求排队

启用排队后,当请求超出速率限制时,请求将被放入队列中,请求在队列中等待,直到许可证可用或发生超时。 根据可配置的队列顺序处理请求。

builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("api", options =>
    {
        options.PermitLimit = 10;           // Allow 10 requests
        options.Window = TimeSpan.FromSeconds(10);  // Per 10-second window
        options.QueueLimit = 5;             // Queue up to 5 additional requests
        options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; // Process oldest requests first
        options.AutoReplenishment = true; // Default: automatically replenish permits
    });
});

EnableRateLimitingDisableRateLimiting 属性

[EnableRateLimiting][DisableRateLimiting] 特性可应用于控制器、操作方法或 Razor 页面。 对于 Razor Pages,这些特性必须应用于 Razor Page,而不是页面处理程序。 例如,[EnableRateLimiting] 不能应用于 OnGetOnPost 或任何其他页面处理程序。

[DisableRateLimiting] 特性禁止对控制器、操作方法或 Page 进行速率限制,而不考虑所应用的命名速率限制器或全球限制器。Razor 例如,请考虑使用以下代码,它会调用 RequireRateLimiting 来对所有控制器终结点应用 fixedPolicy 速率限制:

using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.Configure<MyRateLimitOptions>(
    builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit));

var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
var fixedPolicy = "fixed";

builder.Services.AddRateLimiter(_ => _
    .AddFixedWindowLimiter(policyName: fixedPolicy, options =>
    {
        options.PermitLimit = myOptions.PermitLimit;
        options.Window = TimeSpan.FromSeconds(myOptions.Window);
        options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        options.QueueLimit = myOptions.QueueLimit;
    }));

var slidingPolicy = "sliding";

builder.Services.AddRateLimiter(_ => _
    .AddSlidingWindowLimiter(policyName: slidingPolicy, options =>
    {
        options.PermitLimit = myOptions.SlidingPermitLimit;
        options.Window = TimeSpan.FromSeconds(myOptions.Window);
        options.SegmentsPerWindow = myOptions.SegmentsPerWindow;
        options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        options.QueueLimit = myOptions.QueueLimit;
    }));

var app = builder.Build();
app.UseRateLimiter();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.MapRazorPages().RequireRateLimiting(slidingPolicy);
app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy);

app.Run();

在以下代码中,[DisableRateLimiting] 禁用了速率限制,并替代应用于在 [EnableRateLimiting("fixed")] 中调用的 Home2Controllerapp.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy)Program.cs

[EnableRateLimiting("fixed")]
public class Home2Controller : Controller
{
    private readonly ILogger<Home2Controller> _logger;

    public Home2Controller(ILogger<Home2Controller> logger)
    {
        _logger = logger;
    }

    public ActionResult Index()
    {
        return View();
    }

    [EnableRateLimiting("sliding")]
    public ActionResult Privacy()
    {
        return View();
    }

    [DisableRateLimiting]
    public ActionResult NoLimit()
    {
        return View();
    }

    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
    public IActionResult Error()
    {
        return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
    }
}

在上述代码中,[EnableRateLimiting("sliding")] 没有应用于 操作方法,因为 调用了 Privacy

请考虑以下代码,它没有对 RequireRateLimitingMapRazorPagesMapDefaultControllerRoute 进行调用:

using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.Configure<MyRateLimitOptions>(
    builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit));

var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
var fixedPolicy = "fixed";

builder.Services.AddRateLimiter(_ => _
    .AddFixedWindowLimiter(policyName: fixedPolicy, options =>
    {
        options.PermitLimit = myOptions.PermitLimit;
        options.Window = TimeSpan.FromSeconds(myOptions.Window);
        options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        options.QueueLimit = myOptions.QueueLimit;
    }));

var slidingPolicy = "sliding";

builder.Services.AddRateLimiter(_ => _
    .AddSlidingWindowLimiter(policyName: slidingPolicy, options =>
    {
        options.PermitLimit = myOptions.SlidingPermitLimit;
        options.Window = TimeSpan.FromSeconds(myOptions.Window);
        options.SegmentsPerWindow = myOptions.SegmentsPerWindow;
        options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        options.QueueLimit = myOptions.QueueLimit;
    }));

var app = builder.Build();

app.UseRateLimiter();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.MapRazorPages();
app.MapDefaultControllerRoute();  // RequireRateLimiting not called

app.Run();

考虑以下控制器:

[EnableRateLimiting("fixed")]
public class Home2Controller : Controller
{
    private readonly ILogger<Home2Controller> _logger;

    public Home2Controller(ILogger<Home2Controller> logger)
    {
        _logger = logger;
    }

    public ActionResult Index()
    {
        return View();
    }

    [EnableRateLimiting("sliding")]
    public ActionResult Privacy()
    {
        return View();
    }

    [DisableRateLimiting]
    public ActionResult NoLimit()
    {
        return View();
    }

    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
    public IActionResult Error()
    {
        return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
    }
}

在前一个控制器中:

  • "fixed" 策略速率限制器应用于没有 EnableRateLimitingDisableRateLimiting 特性的所有操作方法。
  • "sliding" 策略速率限制器应用于 Privacy 操作。
  • NoLimit 操作方法上禁用了速率限制。

速率限制指标

速率限制中间件提供 内置指标和监视 功能,以帮助了解速率限制如何影响应用性能和用户体验。 请参阅 Microsoft.AspNetCore.RateLimiting 指标列表。

测试存在速率限制的测试终结点

在将采用速率限制的应用部署到生产环境之前,请对应用进行压力测试,以验证所使用的限制器和选项。 例如,使用 BlazeMeterApache JMeter HTTP(S) 测试脚本记录器等工具创建 JMeter 脚本,并将该脚本加载到 Azure 负载测试

如果使用用户输入的内容创建分区,应用容易受到拒绝服务 (DoS) 攻击。 例如,如果在客户端 IP 地址上创建分区,应用容易受到采用 IP 源地址欺骗的拒绝服务攻击。 有关详细信息,请参阅 BCP 38 RFC 2827 网络入口筛选:抵御采用 IP 源地址欺骗的拒绝服务攻击

其他资源