网站首页 网站源码

using System;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace Dpz.Core.Infrastructure.RateLimiting;
/// <summary>
/// 基于行为的限流中间件
/// 只检查IP是否因可疑行为被限流,不限制正常请求频率
/// </summary>
public class RateLimitMiddleware(
RequestDelegate next,
IIpRateLimitService rateLimitService,
ILogger<RateLimitMiddleware> logger,
RateLimitConfig? config = null
)
{
private readonly RateLimitConfig _config = config ?? new RateLimitConfig();
public async Task InvokeAsync(HttpContext context)
{
var clientIp = context.Request.GetIpAddress();
if (string.IsNullOrEmpty(clientIp))
{
await next(context);
return;
}
// 检查IP是否被限流(不记录任何访问信息)
var result = await rateLimitService.CheckLimitStatusAsync(clientIp, context.RequestAborted);
if (!result.IsAllowed)
{
// IP因可疑行为被限流,延迟后返回429状态码
await HandleBlockedRequest(context, result.Message, result.DelayMs);
return;
}
// IP未被限流,继续处理请求
try
{
await next(context);
}
catch (Exception ex)
{
logger.LogError(ex, "处理请求时发生错误,IP: {ClientIp}", clientIp);
throw;
}
}
/// <summary>
/// 处理被封禁的请求
/// </summary>
private async Task HandleBlockedRequest(HttpContext context, string message, int delayMs = 0)
{
var clientIp = context.Request.GetIpAddress();
// 如果有延迟设置,先让攻击者等待
if (delayMs > 0)
{
// 延迟前检查响应状态
if (context.Response.HasStarted)
{
logger.LogWarning(
"IP {ClientIp} 限流时发现响应已开始,跳过延迟,路径: {RequestPath}",
clientIp,
context.Request.Path
);
return;
}
// 根据配置决定是否记录详细日志
if (_config.EnableVerboseLogging)
{
logger.LogInformation(
"IP {ClientIp} 被限流,延迟 {DelayMs}ms 后返回429,路径: {RequestPath}",
clientIp,
delayMs,
context.Request.Path
);
}
try
{
await Task.Delay(delayMs, context.RequestAborted);
}
catch (OperationCanceledException)
{
// 根据配置决定是否记录详细日志
if (_config.EnableVerboseLogging)
{
logger.LogInformation(
"IP {ClientIp} 在限流延迟期间取消请求,延迟时间: {DelayMs}ms",
clientIp,
delayMs
);
}
return; // 请求被取消,直接返回,不抛出异常
}
}
// 检查响应是否已经开始,避免 "response has already started" 错误
if (context.Response.HasStarted)
{
logger.LogWarning(
"IP {ClientIp} 限流响应时发现响应已开始,无法设置429状态码,路径: {RequestPath}",
clientIp,
context.Request.Path
);
return;
}
// 延迟后返回429响应
context.Response.StatusCode = 429; // Too Many Requests
context.Response.ContentType = "application/json; charset=utf-8";
var response = new
{
error = "IP_BLOCKED",
message,
timestamp = DateTime.UtcNow,
path = context.Request.Path.Value,
delayMs,
};
var jsonResponse = JsonSerializer.Serialize(
response,
new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }
);
await context.Response.WriteAsync(jsonResponse);
logger.LogWarning(
"IP {ClientIp} 被限流并延迟响应,路径: {RequestPath}, 原因: {Message}, 延迟: {DelayMs}ms",
clientIp,
context.Request.Path,
message,
delayMs
);
}
}
上述代码实现了一个基于行为的限流中间件,主要用于在 ASP.NET Core 应用程序中限制可疑 IP 地址的请求频率。以下是代码的主要功能和工作原理的详细解释:
RateLimitMiddleware 是一个中间件类,构造函数接受四个参数:RequestDelegate next:下一个中间件的委托,用于将请求传递给管道中的下一个组件。IIpRateLimitService rateLimitService:一个服务接口,用于检查 IP 地址的限流状态。ILogger<RateLimitMiddleware> logger:用于记录日志的日志记录器。RateLimitConfig? config:可选的配置对象,用于设置限流的相关参数。InvokeAsync 方法是中间件的核心逻辑:rateLimitService 检查该 IP 地址的限流状态。result.IsAllowed 为 false),则调用 HandleBlockedRequest 方法处理被封禁的请求。HandleBlockedRequest 方法用于处理被限流的请求:delayMs 大于 0),则在返回响应之前让请求延迟一段时间。RateLimitConfig 类用于配置限流的相关参数,例如是否启用详细日志记录。这个中间件的主要目的是通过检查 IP 地址的行为来限制可疑请求,防止恶意攻击或滥用。它允许正常请求通过,同时对可疑的 IP 地址实施延迟和限制,确保应用程序的安全性和稳定性。
