using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace Dpz.Core.Infrastructure.RateLimiting;
/// <summary>
/// 窗口期限流中间件
/// 检查 IP 是否处于封禁期,封禁期内延迟响应并返回 429
/// </summary>
public class RateLimitMiddleware(
RequestDelegate next,
IConfiguration configuration,
IWebHostEnvironment webHostEnvironment,
IIpRateLimitService rateLimitService,
ILogger<RateLimitMiddleware> logger
)
{
public async Task InvokeAsync(HttpContext context)
{
// 已登录用户和开发环境不参与限流
if (
context.User.Identity?.IsAuthenticated == true
|| webHostEnvironment.IsDevelopment()
|| IsIgnoredPath(context)
)
{
await next(context);
return;
}
var clientIp = context.Request.GetIpAddress();
if (string.IsNullOrEmpty(clientIp))
{
await next(context);
return;
}
var result = await rateLimitService.CheckLimitStatusAsync(clientIp, context.RequestAborted);
if (result.IsAllowed)
{
await next(context);
return;
}
await RespondBlockedAsync(context, result);
}
/// <summary>
/// 忽略列表内的请求不参与限流,与机器人拦截中间件保持一致
/// </summary>
private bool IsIgnoredPath(HttpContext context)
{
var requestPath = context.Request.Path.Value ?? "";
var ignorePrefixes =
configuration.GetSection("IgnoreRequestPrefix").Get<List<string>>() ?? [];
return ignorePrefixes.Any(prefix =>
requestPath.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)
);
}
/// <summary>
/// 延迟指定时间后返回 429 响应,增加攻击成本
/// </summary>
private async Task RespondBlockedAsync(HttpContext context, RateLimitResult result)
{
var delayMs = result.DelayMs;
if (delayMs > 0 && !context.Response.HasStarted)
{
try
{
await Task.Delay(delayMs, context.RequestAborted);
}
catch (OperationCanceledException)
{
// 客户端已断开,直接结束
return;
}
}
if (context.Response.HasStarted)
{
logger.LogWarning(
"IP {ClientIp} 限流响应时发现响应已开始,无法返回 429,路径: {RequestPath}",
context.Request.GetIpAddress(),
context.Request.Path
);
return;
}
context.Response.StatusCode = StatusCodes.Status429TooManyRequests;
context.Response.ContentType = "application/json; charset=utf-8";
var response = new
{
error = "IP_BLOCKED",
message = result.Message,
timestamp = DateTime.UtcNow,
path = context.Request.Path.Value,
delayMs,
};
await context.Response.WriteAsync(
JsonSerializer.Serialize(
response,
new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }
)
);
logger.LogWarning(
"IP {ClientIp} 处于封禁期,返回 429,路径: {RequestPath},延迟: {DelayMs}ms",
context.Request.GetIpAddress(),
context.Request.Path,
delayMs
);
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
代码解释
这是一个 ASP.NET Core 的限流中间件,用于实现基于窗口期的 IP 访问限流功能。
主要功能
该中间件用于防止某个 IP 地址在短时间内发起过多请求,通过以下方式保护系统:
- 检测 IP 是否被封禁
- 对被封禁的 IP 延迟响应并返回 HTTP 429 状态码
- 增加恶意攻击者的攻击成本
代码结构分析
1. 构造函数(主构造器)
public class RateLimitMiddleware(
RequestDelegate next,
IConfiguration configuration,
IWebHostEnvironment webHostEnvironment,
IIpRateLimitService rateLimitService,
ILogger<RateLimitMiddleware> logger
)
使用 C# 12 的主构造函数语法,依赖注入了以下服务:
next:下一个中间件委托configuration:配置服务(读取忽略路径配置)webHostEnvironment:环境信息(判断是否开发环境)rateLimitService:限流核心服务logger:日志记录器
2. 核心方法:InvokeAsync
处理每个 HTTP 请求的主要逻辑:
白名单逻辑(不参与限流)
if (
context.User.Identity?.IsAuthenticated == true // 已登录用户
|| webHostEnvironment.IsDevelopment() // 开发环境
|| IsIgnoredPath(context) // 配置的忽略路径
)
获取客户端 IP
var clientIp = context.Request.GetIpAddress();
通过扩展方法获取真实客户端 IP(可能考虑代理/负载均衡)
限流检测
var result = await rateLimitService.CheckLimitStatusAsync(clientIp, context.RequestAborted);
调用限流服务检查该 IP 的状态
处理结果
- 允许通过:
await next(context)继续执行后续中间件 - 被封禁:调用
RespondBlockedAsync返回 429 响应
3. IsIgnoredPath 方法
检查当前请求路径是否在忽略列表中:
var ignorePrefixes = configuration.GetSection("IgnoreRequestPrefix").Get<List<string>>();
从配置文件读取忽略的路径前缀(如 /health、/metrics 等),这些路径不参与限流。
4. RespondBlockedAsync 方法
对被封禁的 IP 进行响应处理:
关键设计:延迟响应
await Task.Delay(delayMs, context.RequestAborted);
- 目的:增加攻击者的时间成本,降低攻击效率
- 可取消:当客户端断开连接时捕获
OperationCanceledException
返回 429 状态码
context.Response.StatusCode = StatusCodes.Status429TooManyRequests;
JSON 响应体
{
"error": "IP_BLOCKED",
"message": "具体限流原因",
"timestamp": "2024-01-01T00:00:00Z",
"path": "/api/data",
"delayMs": 5000
}
防御性检查
if (context.Response.HasStarted) { ... }
检测响应是否已经开始发送(避免重复写入响应头)
配置示例
{
"IgnoreRequestPrefix": [
"/health",
"/metrics",
"/swagger"
]
}
设计亮点
- 白名单机制:已认证用户、开发环境、特定路径不受限流影响
- 延迟惩罚:被封禁的请求故意延迟响应,增加 DDoS 攻击成本
- 优雅处理取消:支持客户端中断连接的场景
- 详细日志:记录封禁事件便于监控和排查
- 标准化响应:返回符合 REST 规范的 429 状态码和 JSON 错误信息
应用场景
- 防止暴力破解(登录接口)
- 防止 API 滥用
- 防止爬虫过度抓取
- DDoS 攻击缓解
这是一个生产级的限流中间件实现,具有良好的可扩展性和健壮性。
AI 正在分析代码…
评论加载中...