You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在C#和.NET Core中为公共端点实现限流以缓解暴力攻击

在无请求发起者标识的C#/.NET Core公共端点实现限流

场景说明

这里的“无法识别请求发起者”通常分为两种常见情况:

  • 无用户登录态,但可获取请求的客户端IP(大多数公共端点的常规场景)
  • 所有请求来自统一代理/网关,无法通过IP区分具体发起者(极端场景)

以下是对应场景的具体实现方案:


场景1:基于客户端IP的限流(推荐)

虽然没有用户身份标识,但客户端IP是最可靠的请求来源区分依据,也是公共端点限流的标准做法。可以通过第三方库快速实现,也能自定义中间件完成。

方式1:使用AspNetCoreRateLimit库

这是.NET生态中成熟的限流库,支持IP、路径等多维度规则:

  1. 安装NuGet包:
Install-Package AspNetCoreRateLimit
  1. 在Program.cs中配置服务:
builder.Services.AddMemoryCache();
// 配置IP限流规则
builder.Services.Configure<IpRateLimitOptions>(options =>
{
    options.GeneralRules = new List<RateLimitRule>
    {
        new RateLimitRule
        {
            Endpoint = "*:/api/xx", // 目标端点路径,*匹配任意HTTP方法
            Period = "1m", // 时间窗口(1分钟)
            Limit = 3 // 窗口内允许的请求次数
        }
    };
});
// 注册限流所需的存储和配置服务
builder.Services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();
builder.Services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();
builder.Services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
builder.Services.AddSingleton<IProcessingStrategy, AsyncKeyLockProcessingStrategy>();
  1. 注册中间件(注意要放在路由中间件之前):
app.UseIpRateLimiting();
app.UseRouting();
// 其他中间件...

方式2:自定义IP限流中间件

如果不想依赖第三方库,可以手写轻量中间件:

public class IpRateLimitMiddleware
{
    private readonly RequestDelegate _next;
    // 用并发字典存储IP的请求计数和最后请求时间
    private static readonly ConcurrentDictionary<string, (int Count, DateTime LastRequestTime)> _requestCache = new();
    // 限流参数可根据业务调整
    private const int MaxRequests = 3;
    private const int TimeWindowMinutes = 1;

    public IpRateLimitMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var targetPath = "/api/xx";
        if (!context.Request.Path.Equals(targetPath, StringComparison.OrdinalIgnoreCase))
        {
            await _next(context);
            return;
        }

        var clientIp = context.Connection.RemoteIpAddress?.ToString();
        if (string.IsNullOrEmpty(clientIp))
        {
            context.Response.StatusCode = StatusCodes.Status400BadRequest;
            await context.Response.WriteAsync("无法识别请求来源");
            return;
        }

        var now = DateTime.UtcNow;
        if (_requestCache.TryGetValue(clientIp, out var entry))
        {
            // 检查是否在时间窗口内
            if (now - entry.LastRequestTime <= TimeSpan.FromMinutes(TimeWindowMinutes))
            {
                if (entry.Count >= MaxRequests)
                {
                    context.Response.StatusCode = StatusCodes.Status429TooManyRequests;
                    await context.Response.WriteAsync("请求过于频繁,请稍后再试");
                    return;
                }
                // 更新计数和时间
                _requestCache[clientIp] = (entry.Count + 1, now);
            }
            else
            {
                // 时间窗口过期,重置计数
                _requestCache[clientIp] = (1, now);
            }
        }
        else
        {
            // 首次请求,初始化记录
            _requestCache.TryAdd(clientIp, (1, now));
        }

        await _next(context);
    }
}

然后在Program.cs中注册中间件:

app.UseMiddleware<IpRateLimitMiddleware>();
app.UseRouting();

场景2:全局维度限流(极端场景)

如果完全无法区分任何请求来源(比如所有请求都经过同一代理IP),只能对目标端点设置全局请求次数限制:

自定义全局限流中间件

public class GlobalRateLimitMiddleware
{
    private readonly RequestDelegate _next;
    private static int _globalRequestCount = 0;
    private static DateTime _windowStartTime = DateTime.UtcNow;
    private const int MaxGlobalRequests = 3;
    private const int TimeWindowMinutes = 1;
    // 锁对象保证多线程下计数安全
    private static readonly object _lockObj = new();

    public GlobalRateLimitMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var targetPath = "/api/xx";
        if (!context.Request.Path.Equals(targetPath, StringComparison.OrdinalIgnoreCase))
        {
            await _next(context);
            return;
        }

        lock (_lockObj)
        {
            var now = DateTime.UtcNow;
            // 时间窗口过期则重置计数
            if (now - _windowStartTime > TimeSpan.FromMinutes(TimeWindowMinutes))
            {
                _globalRequestCount = 0;
                _windowStartTime = now;
            }

            if (_globalRequestCount >= MaxGlobalRequests)
            {
                context.Response.StatusCode = StatusCodes.Status429TooManyRequests;
                await context.Response.WriteAsync("当前端点请求过于频繁,请稍后再试");
                return;
            }

            _globalRequestCount++;
        }

        await _next(context);
    }
}

注册方式与自定义IP限流中间件一致。


注意事项

  • 内存存储的限流数据会在应用重启后丢失,若需要持久化,可改用Redis等分布式存储(AspNetCoreRateLimit原生支持Redis)
  • 时间窗口和请求次数需根据业务场景调整,避免误拦截合法请求
  • 返回429 Too Many Requests状态符符合HTTP规范,可额外添加Retry-After响应头告知客户端重试时间
  • 针对IP限流,需考虑同一局域网用户共用IP的情况,可根据业务灵活调整规则

内容的提问来源于stack exchange,提问作者Claudio Riquelme

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 04:52:32