mcpField
D
dotnet-rate-limiting

dotnet-rate-limiting

github.com/ronnythedev安全7760 次安装15 周前更新
代码仓库

关于 dotnet-rate-limiting

面向 ASP.NET Core 开发者的限流配置参考技能,基于 ASP.NET Core 7+ 内置中间件。涵盖多种限流算法、策略分区和端点应用方式,并提供 Redis 分布式限流配置示例。

主要功能

  1. 01固定窗口、滑动窗口、令牌桶和并发限制
  2. 02按用户分区的策略
  3. 03端点与控制器限流配置
  4. 04Redis 分布式限流示例
  5. 05配置与算法选择指南

适用场景

  1. 01限制匿名或已认证请求
  2. 02按用户设置 API 速率限制
  3. 03保护文件上传等并发操作

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/ronnythedev/dotnet-clean-architecture-skills --skill dotnet-rate-limiting

安装后在 Claude Code 中输入/dotnet-rate-limiting确认已加载。

详细介绍

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

概述

内置的速率限制中间件(ASP.NET Core 7+)可保护 API 免受滥用:

  • 固定窗口 - 按固定间隔重置计数器
  • 滑动窗口 - 平滑的固定窗口
  • 令牌桶 - 允许突发请求,并随时间补充令牌
  • 并发限制 - 限制同时处理的请求数

快速参考

算法最适用场景特征
固定窗口简单的速率限制计数器在间隔边界处重置
滑动窗口更平滑的限制按分段计算加权平均值
令牌桶容忍突发流量允许突发请求,最多不超过桶容量
并发限制资源保护限制并行请求数

速率限制结构

/API/RateLimiting/
├── RateLimitingConfiguration.cs
├── Policies/
│   ├── UserRateLimitPolicy.cs
│   └── IpRateLimitPolicy.cs
└── Middleware/
    └── RateLimitExceededHandler.cs

模板:基本速率限制配置

// src/{name}.api/Program.cs
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.RateLimiting;

var builder = WebApplication.CreateBuilder(args);

// ═══════════════════════════════════════════════════════════════
// RATE LIMITING CONFIGURATION
// ═══════════════════════════════════════════════════════════════
builder.Services.AddRateLimiter(options =>
{
    // Global limiter settings
    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;

    // ═══════════════════════════════════════════════════════════════
    // FIXED WINDOW POLICY
    // Allows N requests per time window, resets at window boundary
    // ═══════════════════════════════════════════════════════════════
    options.AddFixedWindowLimiter("fixed", limiterOptions =>
    {
        limiterOptions.PermitLimit = 100;                           // Requests per window
        limiterOptions.Window = TimeSpan.FromMinutes(1);            // Window duration
        limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        limiterOptions.QueueLimit = 10;                             // Queue when limit exceeded
    });

    // ═══════════════════════════════════════════════════════════════
    // SLIDING WINDOW POLICY
    // Smoother rate limiting with segments
    // ═══════════════════════════════════════════════════════════════
    options.AddSlidingWindowLimiter("sliding", limiterOptions =>
    {
        limiterOptions.PermitLimit = 100;
        limiterOptions.Window = TimeSpan.FromMinutes(1);
        limiterOptions.SegmentsPerWindow = 6;                       // 6 segments = 10s each
        limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        limiterOptions.QueueLimit = 10;
    });

    // ═══════════════════════════════════════════════════════════════
    // TOKEN BUCKET POLICY
    // Allows bursts, then rate-limited
    // ═══════════════════════════════════════════════════════════════
    options.AddTokenBucketLimiter("token", limiterOptions =>
    {
        limiterOptions.TokenLimit = 100;                            // Maximum tokens (bucket size)
        limiterOptions.ReplenishmentPeriod = TimeSpan.FromSeconds(1);
        limiterOptions.TokensPerPeriod = 10;                        // Tokens added per period
        limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        limiterOptions.QueueLimit = 10;
        limiterOptions.AutoReplenishment = true;
    });

    // ═══════════════════════════════════════════════════════════════
    // CONCURRENCY LIMITER
    // Limits simultaneous requests (not rate)
    // ═══════════════════════════════════════════════════════════════
    options.AddConcurrencyLimiter("concurrency", limiterOptions =>
    {
        limiterOptions.PermitLimit = 50;                            // Max concurrent requests
        limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        limiterOptions.QueueLimit = 25;
    });

    // Custom response for rejected requests
    options.OnRejected = async (context, cancellationToken) =>
    {
        if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
        {
            context.HttpContext.Response.Headers.RetryAfter =
                ((int)retryAfter.TotalSeconds).ToString();
        }

        context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
        await context.HttpContext.Response.WriteAsJsonAsync(new
        {
            error = "Too many requests",
            message = "Rate limit exceeded. Please try again later.",
            retryAfterSeconds = retryAfter?.TotalSeconds ?? 60
        }, cancellationToken);
    };
});

var app = builder.Build();

// ═══════════════════════════════════════════════════════════════
// MIDDLEWARE ORDER - Rate limiting before authentication
// ═══════════════════════════════════════════════════════════════
app.UseRateLimiter();
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

模板:基于策略的速率限制

// src/{name}.api/RateLimiting/RateLimitPolicies.cs
namespace {name}.api.ratelimiting;

public static class RateLimitPolicies
{
    // Policy names
    public const string Anonymous = "anonymous";
    public const string Authenticated = "authenticated";
    public const string Premium = "premium";
    public const string Api = "api";
    public const string Upload = "upload";
}
// src/{name}.api/RateLimiting/RateLimitingConfiguration.cs
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.RateLimiting;

namespace {name}.api.ratelimiting;

public static class RateLimitingConfiguration
{
    public static IServiceCollection AddRateLimitingPolicies(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        var settings = configuration
            .GetSection("RateLimiting")
            .Get<RateLimitSettings>() ?? new RateLimitSettings();

        services.AddRateLimiter(options =>
        {
            options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;

            // ═══════════════════════════════════════════════════════════════
            // ANONYMOUS USERS - Stricter limits
            // ═══════════════════════════════════════════════════════════════
            options.AddFixedWindowLimiter(RateLimitPolicies.Anonymous, limiterOptions =>
            {
                limiterOptions.PermitLimit = settings.AnonymousRequestsPerMinute;
                limiterOptions.Window = TimeSpan.FromMinutes(1);
                limiterOptions.QueueLimit = 5;
            });

            // ═══════════════════════════════════════════════════════════════
            // AUTHENTICATED USERS - More generous limits
            // ═══════════════════════════════════════════════════════════════
            options.AddTokenBucketLimiter(RateLimitPolicies.Authenticated, limiterOptions =>
            {
                limiterOptions.TokenLimit = settings.AuthenticatedBurstLimit;
                limiterOptions.ReplenishmentPeriod = TimeSpan.FromSeconds(1);
                limiterOptions.TokensPerPeriod = settings.AuthenticatedTokensPerSecond;
                limiterOptions.QueueLimit = 20;
            });

            // ═══════════════════════════════════════════════════════════════
            // PREMIUM USERS - Highest limits
            // ═══════════════════════════════════════════════════════════════
            options.AddTokenBucketLimiter(RateLimitPolicies.Premium, limiterOptions =>
            {
                limiterOptions.TokenLimit = settings.PremiumBurstLimit;
                limiterOptions.ReplenishmentPeriod = TimeSpan.FromSeconds(1);
                limiterOptions.TokensPerPeriod = settings.PremiumTokensPerSecond;
                limiterOptions.QueueLimit = 50;
            });

            // ═══════════════════════════════════════════════════════════════
            // API ENDPOINTS - Per-endpoint limiting
            // ═══════════════════════════════════════════════════════════════
            options.AddSlidingWindowLimiter(RateLimitPolicies.Api, limiterOptions =>
            {
                limiterOptions.PermitLimit = settings.ApiRequestsPerMinute;
                limiterOptions.Window = TimeSpan.FromMinutes(1);
                limiterOptions.SegmentsPerWindow = 6;
                limiterOptions.QueueLimit = 10;
            });

            // ═══════════════════════════════════════════════════════════════
            // FILE UPLOADS - Concurrency limited
            // ═══════════════════════════════════════════════════════════════
            options.AddConcurrencyLimiter(RateLimitPolicies.Upload, limiterOptions =>
            {
                limiterOptions.PermitLimit = settings.MaxConcurrentUploads;
                limiterOptions.QueueLimit = settings.UploadQueueSize;
            });

            // Global rejection handler
            options.OnRejected = HandleRejection;
        });

        return services;
    }

    private static async ValueTask HandleRejection(
        OnRejectedContext context,
        CancellationToken cancellationToken)
    {
        var response = context.HttpContext.Response;

        // Set retry-after header if available
        if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
        {
            response.Headers.RetryAfter = ((int)retryAfter.TotalSeconds).ToString();
        }

        // Log the rejection
        var logger = context.HttpContext.RequestServices
            .GetRequiredService<ILogger<RateLimitingConfiguration>>();

        var clientIp = context.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
        var path = context.HttpContext.Request.Path;

        logger.LogWarning(
            "Rate limit exceeded for client {ClientIp} on path {Path}",
            clientIp,
            path);

        response.StatusCode = StatusCodes.Status429TooManyRequests;
        response.ContentType = "application/json";

        await response.WriteAsJsonAsync(new
        {
            type = "https://tools.ietf.org/html/rfc6585#section-4",
            title = "Too Many Requests",
            status = 429,
            detail = "Rate limit exceeded. Please slow down your requests.",
            retryAfter = retryAfter?.TotalSeconds ?? 60
        }, cancellationToken);
    }
}
设置类
// src/{name}.api/RateLimiting/RateLimitSettings.cs
namespace {name}.api.ratelimiting;

public sealed class RateLimitSettings
{
    public int AnonymousRequestsPerMinute { get; set; } = 30;
    public int AuthenticatedBurstLimit { get; set; } = 100;
    public int AuthenticatedTokensPerSecond { get; set; } = 10;
    public int PremiumBurstLimit { get; set; } = 500;
    public int PremiumTokensPerSecond { get; set; } = 50;
    public int ApiRequestsPerMinute { get; set; } = 60;
    public int MaxConcurrentUploads { get; set; } = 5;
    public int UploadQueueSize { get; set; } = 10;
}
appsettings.json
{
  "RateLimiting": {
    "AnonymousRequestsPerMinute": 30,
    "AuthenticatedBurstLimit": 100,
    "AuthenticatedTokensPerSecond": 10,
    "PremiumBurstLimit": 500,
    "PremiumTokensPerSecond": 50,
    "ApiRequestsPerMinute": 60,
    "MaxConcurrentUploads": 5,
    "UploadQueueSize": 10
  }
}

模板:基于用户的速率限制(分区)

// src/{name}.api/Program.cs
builder.Services.AddRateLimiter(options =>
{
    // ═══════════════════════════════════════════════════════════════
    // PARTITIONED BY USER ID
    // Each user has their own rate limit bucket
    // ═══════════════════════════════════════════════════════════════
    options.AddPolicy("per-user", httpContext =>
    {
        var userId = httpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);

        if (string.IsNullOrEmpty(userId))
        {
            // Anonymous users - stricter limit, partition by IP
            var clientIp = httpContext.Connection.RemoteIpAddress?.ToString() ?? "anonymous";

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

        // Authenticated users - more generous
        return RateLimitPartition.GetTokenBucketLimiter(
            partitionKey: userId,
            factory: _ => new TokenBucketRateLimiterOptions
            {
                TokenLimit = 100,
                ReplenishmentPeriod = TimeSpan.FromSeconds(1),
                TokensPerPeriod = 10
            });
    });

    // ═══════════════════════════════════════════════════════════════
    // PARTITIONED BY SUBSCRIPTION TIER
    // Different limits based on user's subscription
    // ═══════════════════════════════════════════════════════════════
    options.AddPolicy("tier-based", httpContext =>
    {
        var userId = httpContext.User.FindFirstValue(ClaimTypes.NameIdentifier) ?? "anonymous";
        var tier = httpContext.User.FindFirstValue("subscription_tier") ?? "free";

        return tier switch
        {
            "enterprise" => RateLimitPartition.GetNoLimiter(userId),

            "premium" => RateLimitPartition.GetTokenBucketLimiter(
                partitionKey: userId,
                factory: _ => new TokenBucketRateLimiterOptions
                {
                    TokenLimit = 500,
                    ReplenishmentPeriod = TimeSpan.FromSeconds(1),
                    TokensPerPeriod = 50
                }),

            "basic" => RateLimitPartition.GetTokenBucketLimiter(
                partitionKey: userId,
                factory: _ => new TokenBucketRateLimiterOptions
                {
                    TokenLimit = 100,
                    ReplenishmentPeriod = TimeSpan.FromSeconds(1),
                    TokensPerPeriod = 10
                }),

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

模板:对端点应用速率限制

Minimal API 端点
// src/{name}.api/Endpoints/OrderEndpoints.cs
public static class OrderEndpoints
{
    public static void MapOrderEndpoints(this IEndpointRouteBuilder app)
    {
        var group = app.MapGroup("/api/orders")
            .WithTags("Orders")
            .RequireAuthorization();

        // ═══════════════════════════════════════════════════════════════
        // APPLY RATE LIMIT TO SINGLE ENDPOINT
        // ═══════════════════════════════════════════════════════════════
        group.MapGet("/", GetOrders)
            .RequireRateLimiting(RateLimitPolicies.Api);

        group.MapGet("/{id:guid}", GetOrderById)
            .RequireRateLimiting(RateLimitPolicies.Api);

        // ═══════════════════════════════════════════════════════════════
        // STRICTER LIMIT FOR WRITE OPERATIONS
        // ═══════════════════════════════════════════════════════════════
        group.MapPost("/", CreateOrder)
            .RequireRateLimiting(RateLimitPolicies.Authenticated);

        // ═══════════════════════════════════════════════════════════════
        // DISABLE RATE LIMITING FOR SPECIFIC ENDPOINT
        // ═══════════════════════════════════════════════════════════════
        group.MapGet("/health", () => Results.Ok())
            .DisableRateLimiting();
    }
}
控制器
// src/{name}.api/Controllers/OrdersController.cs
using Microsoft.AspNetCore.RateLimiting;

namespace {name}.api.controllers;

[ApiController]
[Route("api/[controller]")]
[EnableRateLimiting(RateLimitPolicies.Api)]  // Apply to all actions
public sealed class OrdersController : ControllerBase
{
    [HttpGet]
    public async Task<IActionResult> GetOrders()
    {
        // Uses controller-level rate limit
    }

    [HttpGet("{id:guid}")]
    public async Task<IActionResult> GetOrderById(Guid id)
    {
        // Uses controller-level rate limit
    }

    [HttpPost]
    [EnableRateLimiting(RateLimitPolicies.Authenticated)]  // Override for this action
    public async Task<IActionResult> CreateOrder([FromBody] CreateOrderRequest request)
    {
        // Uses action-level rate limit
    }

    [HttpGet("export")]
    [EnableRateLimiting(RateLimitPolicies.Premium)]  // Expensive operation
    public async Task<IActionResult> ExportOrders()
    {
        // Uses premium rate limit
    }

    [HttpGet("health")]
    [DisableRateLimiting]  // Health checks shouldn't be rate limited
    public IActionResult Health()
    {
        return Ok();
    }
}
组级别的速率限制
// src/{name}.api/Endpoints/PublicApiEndpoints.cs
public static void MapPublicApiEndpoints(this IEndpointRouteBuilder app)
{
    // Apply rate limit to entire group
    var group = app.MapGroup("/api/public")
        .RequireRateLimiting(RateLimitPolicies.Anonymous);

    group.MapGet("/products", GetProducts);
    group.MapGet("/categories", GetCategories);
}

模板:使用 Redis 实现分布式速率限制

// src/{name}.api/RateLimiting/RedisRateLimitingConfiguration.cs
using System.Threading.RateLimiting;
using StackExchange.Redis;

namespace {name}.api.ratelimiting;

/// <summary>
/// For distributed systems, implement custom rate limiter with Redis
/// to share rate limit state across multiple instances.
/// </summary>
public static class RedisRateLimitingConfiguration
{
    public static IServiceCollection AddDistributedRateLimiting(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        var redisConnection = configuration.GetConnectionString("Redis");

        services.AddSingleton<IConnectionMultiplexer>(
            ConnectionMultiplexer.Connect(redisConnection!));

        services.AddRateLimiter(options =>
        {
            options.AddPolicy("distributed", context =>
            {
                var redis = context.RequestServices
                    .GetRequiredService<IConnectionMultiplexer>();

                var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier)
                    ?? context.Connection.RemoteIpAddress?.ToString()
                    ?? "anonymous";

                return RateLimitPartition.Get(
                    partitionKey: userId,
                    factory: key => new RedisRateLimiter(
                        redis: redis,
                        partitionKey: key,
                        options: new RedisRateLimiterOptions
                        {
                            PermitLimit = 100,
                            Window = TimeSpan.FromMinutes(1)
                        }));
            });
        });

        return services;
    }
}

/// <summary>
/// Custom rate limiter using Redis for distributed state
/// </summary>
public sealed class RedisRateLimiter : RateLimiter
{
    private readonly IConnectionMultiplexer _redis;
    private readonly string _partitionKey;
    private readonly RedisRateLimiterOptions _options;

    public RedisRateLimiter(
        IConnectionMultiplexer redis,
        string partitionKey,
        RedisRateLimiterOptions options)
    {
        _redis = redis;
        _partitionKey = partitionKey;
        _options = options;
    }

    public override TimeSpan? IdleDuration => null;

    public override RateLimiterStatistics? GetStatistics() => null;

    protected override async ValueTask<RateLimitLease> AcquireAsyncCore(
        int permitCount,
        CancellationToken cancellationToken)
    {
        var db = _redis.GetDatabase();
        var key = $"rate_limit:{_partitionKey}";
        var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
        var windowStart = now - (long)_options.Window.TotalSeconds;

        // Remove old entries outside the window
        await db.SortedSetRemoveRangeByScoreAsync(key, 0, windowStart);

        // Count current requests in window
        var currentCount = await db.SortedSetLengthAsync(key);

        if (currentCount >= _options.PermitLimit)
        {
            // Get oldest entry to calculate retry-after
            var oldest = await db.SortedSetRangeByRankWithScoresAsync(key, 0, 0);
            var retryAfter = oldest.Length > 0
                ? TimeSpan.FromSeconds(oldest[0].Score + _options.Window.TotalSeconds - now)
                : _options.Window;

            return new RedisRateLimitLease(false, retryAfter);
        }

        // Add new request
        await db.SortedSetAddAsync(key, Guid.NewGuid().ToString(), now);
        await db.KeyExpireAsync(key, _options.Window.Add(TimeSpan.FromMinutes(1)));

        return new RedisRateLimitLease(true, null);
    }

    protected override RateLimitLease AttemptAcquireCore(int permitCount)
    {
        // Synchronous not supported for Redis
        return new RedisRateLimitLease(false, _options.Window);
    }
}

public sealed class RedisRateLimitLease : RateLimitLease
{
    private readonly TimeSpan? _retryAfter;

    public RedisRateLimitLease(bool isAcquired, TimeSpan? retryAfter)
    {
        IsAcquired = isAcquired;
        _retryAfter = retryAfter;
    }

    public override bool IsAcquired { get; }

    public override IEnumerable<string> MetadataNames =>
        _retryAfter.HasValue ? new[] { MetadataName.RetryAfter.Name } : Array.Empty<string>();

    public override bool TryGetMetadata(string metadataName, out object? metadata)
    {
        if (metadataName == MetadataName.RetryAfter.Name && _retryAfter.HasValue)
        {
            metadata = _retryAfter.Value;
            return true;
        }

        metadata = null;
        return false;
    }
}

public sealed class RedisRateLimiterOptions
{
    public int PermitLimit { get; set; } = 100;
    public TimeSpan Window { get; set; } = TimeSpan.FromMinutes(1);
}

算法选择指南

┌────────────────────────────────────────────────────────────────┐
│                Which Rate Limiting Algorithm?                   │
├────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Need to limit concurrent requests (not rate)?                  │
│       │                                                         │
│       └── YES ──► Use Concurrency Limiter                       │
│                   (file uploads, heavy operations)              │
│                                                                 │
│  Need to allow bursts?                                          │
│       │                                                         │
│       └── YES ──► Use Token Bucket                              │
│                   (API calls, user actions)                     │
│                                                                 │
│  Need smooth rate limiting?                                     │
│       │                                                         │
│       └── YES ──► Use Sliding Window                            │
│                   (API rate limits, quota enforcement)          │
│                                                                 │
│  Simple time-based limit?                                       │
│       │                                                         │
│       └── YES ──► Use Fixed Window                              │
│                   (login attempts, simple limits)               │
│                                                                 │
└────────────────────────────────────────────────────────────────┘

关键规则

  1. 选择合适的算法 - 突发流量使用令牌桶,平滑限制使用滑动窗口
  2. 按用户分区 - 避免仅按 IP 分区(NAT 问题)
  3. 设置队列限制 - 防止内存耗尽
  4. 包含 Retry-After 标头 - 帮助客户端正确退避
  5. 记录拒绝事件 - 监控滥用模式
  6. 进行负载测试 - 验证限制是否符合预期
  7. 使用分布式状态 - 多实例部署时使用 Redis
  8. 将健康检查排除在外 - 不要限制监控端点的速率
  9. 按层级设置不同限制 - 高级用户可获得更高限制
  10. 中间件顺序很重要 - 对匿名用户应用限制时,将中间件放在身份验证之前

应避免的反模式

// ❌ WRONG: Partition only by IP (NAT can share IPs)
options.AddPolicy("bad", context =>
    RateLimitPartition.GetFixedWindowLimiter(
        context.Connection.RemoteIpAddress?.ToString() ?? "unknown",
        _ => new FixedWindowRateLimiterOptions { /* ... */ }));

// ✅ CORRECT: Prefer user ID, fallback to IP
options.AddPolicy("good", context =>
{
    var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);
    var partitionKey = userId ?? context.Connection.RemoteIpAddress?.ToString() ?? "anonymous";
    return RateLimitPartition.GetFixedWindowLimiter(partitionKey, /* ... */);
});

// ❌ WRONG: No queue limit (memory exhaustion risk)
options.AddFixedWindowLimiter("bad", limiter =>
{
    limiter.PermitLimit = 100;
    limiter.Window = TimeSpan.FromMinutes(1);
    // Missing QueueLimit!
});

// ✅ CORRECT: Always set queue limit
options.AddFixedWindowLimiter("good", limiter =>
{
    limiter.PermitLimit = 100;
    limiter.Window = TimeSpan.FromMinutes(1);
    limiter.QueueLimit = 10;  // Bounded queue
});

// ❌ WRONG: Rate limiting health endpoints
app.MapGet("/health", () => Results.Ok())
    .RequireRateLimiting("api");

// ✅ CORRECT: Exempt health checks
app.MapGet("/health", () => Results.Ok())
    .DisableRateLimiting();

// ❌ WRONG: Same limits for all users
options.AddFixedWindowLimiter("api", limiter =>
{
    limiter.PermitLimit = 100;  // Same for everyone
});

// ✅ CORRECT: Tier-based limits
options.AddPolicy("api", context =>
{
    var tier = context.User.FindFirstValue("tier") ?? "free";
    var limit = tier switch { "premium" => 500, "basic" => 100, _ => 20 };
    return RateLimitPartition.GetFixedWindowLimiter(/* ... */);
});

相关技能

  • 07-minimal-api-endpoints - 对端点应用速率限制
  • 12-dotnet-jwt-authentication - 用于分区的用户识别
  • 24-logging-configuration - 记录速率限制事件
  • 17-dotnet-health-checks - 将健康检查排除在速率限制之外

评论

支持 Markdown