网站首页 网站源码

这是一个基于行为的分布式IP限流服务,专注于限制可疑行为而非正常请求频率。适用于多服务器部署和负载均衡环境。核心理念:
// Program.cs 或 Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// 首先确保已注册 FusionCache (通常在主项目中已配置)
services.AddFusionCache()
.WithDistributedCache(...)
.WithBackplane(...);
// 注册限流服务
services.AddScoped<IIpRateLimitService, IpRateLimitService>();
// 配置限流参数
services.Configure<RateLimitConfig>(config =>
{
config.WindowSizeMinutes = 5;
config.MaxBlocksPerWindow = 3;
config.BlockDurationMinutes = 30;
config.BlockedDelayBaseMs = 2000;
config.BlockedDelayMaxMs = 10000;
});
}
public void Configure(IApplicationBuilder app)
{
// 添加限流中间件(必须在 RejectBots 之前)
app.UseMiddleware<RateLimitMiddleware>();
// 添加安全检查中间件
app.UseMiddleware<RejectBots>();
// 其他中间件
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints => { ... });
}
appsettings.json
{
"RateLimit": {
"WindowSizeMinutes": 5,
"MaxBlocksPerWindow": 3,
"BlockDurationMinutes": 30,
"CleanupIntervalMinutes": 60,
"BlockedDelayBaseMs": 2000,
"BlockedDelayMaxMs": 10000
}
}
| 参数 | 默认值 | 说明 | 建议值 |
|---|---|---|---|
WindowSizeMinutes | 5 | 统计时间窗口(分钟) | 5-15分钟 |
MaxBlocksPerWindow | 3 | 窗口内最大拦截次数 | 3-5次 |
BlockDurationMinutes | 30 | 封禁持续时间(分钟) | 15-60分钟 |
CleanupIntervalMinutes | 60 | 清理间隔(分钟) | 30-120分钟 |
BlockedDelayBaseMs | 2000 | 被限流时的基础延迟(毫秒) | 1000-3000ms |
BlockedDelayMaxMs | 10000 | 被限流时的最大延迟(毫秒) | 5000-30000ms |
正常请求: 无限制,立即处理
可疑行为: 被 RejectBots 拦截时计数
达到阈值: 限流封禁,延迟处理所有请求
时间窗口: 5分钟内
拦截次数: 被安全规则拦截 3 次
限流时长: 30分钟
延迟处理: 2秒-10秒渐进式延迟
配置: BlockedDelayBaseMs=2000, BlockedDelayMaxMs=10000
拦截次数 | 延迟时间
1-3 | 2000ms (基础延迟)
4-6 | 4000ms
7-9 | 6000ms
10+ | 10000ms (最大延迟)
传统限流: 限制所有请求频率
行为限流: 只限制可疑行为
例如:
- 正常用户快速浏览: ✅ 允许
- 正常API高频调用: ✅ 允许
- 访问非法路径: ❌ 计数
- 恶意爬虫行为: ❌ 计数
- SQL注入尝试: ❌ 计数
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 服务器 A │ │ 服务器 B │ │ 服务器 C │
│ │ │ │ │ │
│ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │
│ │ FusionCache │ │ │ │ FusionCache │ │ │ │ FusionCache │ │
│ │ (L1 Cache) │ │ │ │ (L1 Cache) │ │ │ │ (L1 Cache) │ │
│ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │
└─────────┬───────┘ └─────────┬───────┘ └─────────┬───────┘
│ │ │
└──────────────────────┼──────────────────────┘
│
┌─────────────▼───────────┐
│ Redis Cluster │
│ (分布式缓存 + 消息) │
└─────────────────────────┘
客户端请求
↓
RateLimitMiddleware
├─ 检查IP是否被限流
├─ 如被限流: 延迟 + 返回429
└─ 未限流: 继续处理
↓
RejectBots
├─ 安全规则检查
├─ 发现可疑行为: 调用 RecordBlockAsync()
└─ 正常请求: 继续处理
↓
业务逻辑处理
缓存键格式: "RateLimit:IP:{IP地址}"
示例: "RateLimit:IP:192.168.1.100"
过期时间: 窗口大小 + 清理间隔(自动管理)
客户端请求 → 检查限流(通过) → 安全检查(通过) → 业务处理 → 返回结果
耗时: 正常业务处理时间(毫秒级)
客户端请求 → 检查限流(通过) → 安全检查(拦截) → 记录拦截 → 返回403
耗时: 毫秒级(不处理业务逻辑)
客户端请求 → 检查限流(拦截) → 延迟等待 → 返回429
耗时: 2-10秒延迟时间(惩罚机制)
/// <summary>
/// 基于行为的IP限流服务接口
/// 专注于限制可疑行为,不限制正常请求频率
/// </summary>
public interface IIpRateLimitService
{
/// <summary>
/// 检查IP是否被限流
/// 只检查状态,不记录任何访问信息
/// </summary>
Task<RateLimitResult> CheckLimitStatusAsync(
string ip,
CancellationToken cancellationToken = default
);
/// <summary>
/// 记录IP被拦截事件
/// 当安全中间件检测到可疑行为时调用
/// </summary>
Task RecordBlockAsync(string ip, string? reason = null);
}
/// <summary>
/// 基于行为的限流结果
/// 支持对被限流的请求进行延迟处理
/// </summary>
public class RateLimitResult
{
/// <summary>是否允许请求</summary>
public bool IsAllowed { get; init; }
/// <summary>是否被限流(需要延迟后返回429)</summary>
public bool IsBlocked { get; init; }
/// <summary>延迟时间(毫秒)- 仅在被限流时使用</summary>
public int DelayMs { get; init; }
/// <summary>结果消息</summary>
public string Message { get; init; } = string.Empty;
public static RateLimitResult Allow() => new() { IsAllowed = true };
public static RateLimitResult Block(string message, int delayMs = 0) =>
new()
{
IsAllowed = false,
IsBlocked = true,
Message = message,
DelayMs = delayMs
};
}
职责分离:
- RateLimitMiddleware: 专注限流检查和执行
- RejectBots: 专注安全规则检查和拦截记录
- 两者协同工作,实现基于行为的智能限流
public class RejectBots
{
private readonly IIpRateLimitService _rateLimitService;
public async Task InvokeAsync(HttpContext context)
{
var clientIp = GetClientIp(context);
// 检查各种安全规则
if (IsOldBrowser(context))
{
await _rateLimitService.RecordBlockAsync(clientIp, "Old browser blocked");
await Response403Async(context);
return;
}
if (IsInBlacklist(context))
{
await _rateLimitService.RecordBlockAsync(clientIp, "Blacklist hit");
await Response403Async(context);
return;
}
if (IsEmptyUserAgent(context))
{
await _rateLimitService.RecordBlockAsync(clientIp, "Empty User-Agent");
await Response403Async(context);
return;
}
if (MatchesInterceptRules(context))
{
await _rateLimitService.RecordBlockAsync(clientIp, "Custom rule hit");
await Response403Async(context);
return;
}
await _next(context);
}
}
public class CustomIpRateLimitService : IpRateLimitService
{
protected override int CalculateBlockedDelay(IpAccessRecord record)
{
// 自定义延迟计算逻辑
var baseDelay = _config.BlockedDelayBaseMs;
var maxDelay = _config.BlockedDelayMaxMs;
// 基于拦截次数的指数增长
var multiplier = Math.Pow(2, Math.Min(record.BlockCount - 1, 4));
var calculatedDelay = (int)(baseDelay * multiplier);
return Math.Min(calculatedDelay, maxDelay);
}
}
public class WhitelistRateLimitService : IIpRateLimitService
{
private readonly HashSet<string> _whitelist = new()
{
"127.0.0.1", "::1", // 本地地址
"10.0.0.0/8", // 内网地址 (需要实现CIDR匹配)
};
public async Task<RateLimitResult> CheckLimitStatusAsync(
string ip,
CancellationToken cancellationToken = default)
{
if (IsWhitelisted(ip))
{
return RateLimitResult.Allow();
}
return await _baseService.CheckLimitStatusAsync(ip, cancellationToken);
}
public async Task RecordBlockAsync(string ip, string? reason = null)
{
if (!IsWhitelisted(ip))
{
await _baseService.RecordBlockAsync(ip, reason);
}
}
}
[ApiController]
public class DiagnosticsController : ControllerBase
{
private readonly IFusionCache _fusionCache;
[HttpGet("rate-limit/status/{ip}")]
public async Task<IActionResult> GetIpStatus(string ip)
{
var cacheKey = $"RateLimit:IP:{ip}";
var record = await _fusionCache.GetOrDefaultAsync<IpAccessRecord>(cacheKey);
if (record == null)
{
return Ok(new { Status = "Clean", Message = "No record found" });
}
return Ok(new
{
Status = record.BlockedUntil?.ToString("yyyy-MM-dd HH:mm:ss UTC"),
BlockCount = record.BlockCount,
WindowStart = record.WindowStart.ToString("yyyy-MM-dd HH:mm:ss UTC"),
LastBlockTime = record.LastBlockTime.ToString("yyyy-MM-dd HH:mm:ss UTC")
});
}
}
// 开发环境 - 较宽松的配置
WindowSizeMinutes = 10
MaxBlocksPerWindow = 5
BlockDurationMinutes = 15
// 生产环境 - 严格的配置
WindowSizeMinutes = 5
MaxBlocksPerWindow = 3
BlockDurationMinutes = 30
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Dpz.Core.Infrastructure.RateLimiting": "Information",
"FusionCache": "Warning"
}
}
}
public async Task<RateLimitResult> CheckLimitStatusAsync(
string ip,
CancellationToken cancellationToken = default)
{
try
{
return await _normalCheckLogic(ip, cancellationToken);
}
catch (Exception ex) when (ex is RedisException or TimeoutException)
{
_logger.LogWarning(ex, "限流服务降级,允许通过: {Ip}", ip);
// 降级策略:Redis不可用时允许通过
return RateLimitResult.Allow();
}
}
MaxRequestsPerWindow 等频率相关配置CheckAndApplyRateLimitAsync 拆分为 CheckLimitStatusAsync 和 RecordBlockAsyncRateLimitMiddleware 在 RejectBots 之前// 旧版本调用
var result = await rateLimitService.CheckAndApplyRateLimitAsync(ip);
await rateLimitService.RecordAccessAsync(ip); // 移除此调用
// 新版本调用
var result = await rateLimitService.CheckLimitStatusAsync(ip);
// RecordBlockAsync 由 RejectBots 在检测到可疑行为时调用
这个基于行为的分布式IP限流服务提供了:
通过这种基于行为的设计,你的应用既能有效防护恶意攻击,又能为正常用户提供无感的高性能体验!
