using Dpz.Core.Infrastructure.Intercept;
using Dpz.Core.Infrastructure.RateLimiting;
using Dpz.Core.Public.Entity;
using Microsoft.Extensions.Primitives;
using Wangkanai.Detection.Models;
using Wangkanai.Detection.Services;
namespace Dpz.Core.Web.Library.Middleware;
public class RejectBotsMiddleware(
RequestDelegate next,
IConfiguration configuration,
IWebHostEnvironment webHostEnvironment,
ILogger<RejectBotsMiddleware> logger,
IIpRateLimitService rateLimitService,
IInterceptRuleService interceptRuleService
)
{
public async Task Invoke(
HttpContext httpContext,
IBlockService blockService,
IDetectionService detectionService
)
{
// 0. 已登录用户直接忽略
if (httpContext.User.Authenticated
#if DEBUG
|| webHostEnvironment.IsDevelopment()
#endif
)
{
await next.Invoke(httpContext);
return;
}
var requestPath = httpContext.Request.Path.Value ?? "";
var ignoreRequestPrefixes =
configuration.GetSection("IgnoreRequestPrefix").Get<List<string>>() ?? [];
// 1. 忽略列表优先处理(IgnoreRequestPrefix)
// 如果当前规则在忽略列表内,则彻底忽略,不进行下一步
if (
ignoreRequestPrefixes.Any(x =>
requestPath.StartsWith(x, StringComparison.OrdinalIgnoreCase)
)
)
{
await next.Invoke(httpContext);
return;
}
var clientIp = httpContext.Request.GetIpAddress();
// 2. 检查自定义拦截规则(规则 > 黑名单)
// 如果命中规则,下一步,并且记录
var rule = interceptRuleService.CheckInterceptRules(httpContext);
if (rule != null)
{
logger.LogInformation(
"命中规则拦截,RequestPath: {RequestPath},Rule:{@Rule}",
httpContext.Request.Path,
rule
);
// 记录IP拦截
await rateLimitService.RecordBlockAsync(clientIp, "命中拦截规则");
await SaveBlockAndResponse403Async(blockService, httpContext);
return;
}
// 拦截空 User-Agent 请求 (也可视为规则的一种)
if (StringValues.IsNullOrEmpty(httpContext.Request.Headers.UserAgent))
{
logger.LogInformation(
"空User-Agent拦截,RequestPath:{RequestPath}",
httpContext.Request.Path
);
// 记录IP拦截
await rateLimitService.RecordBlockAsync(clientIp, "空User-Agent");
await SaveBlockAndResponse403Async(blockService, httpContext);
return;
}
// 拦截特定浏览器 (也可视为规则的一种)
if (
(
detectionService.Browser.Name is Browser.Chrome or Browser.Firefox or Browser.Edge
&& detectionService.Browser.Version < new Version("100.0.0.0")
)
|| detectionService.Browser.Name is Browser.InternetExplorer
)
{
logger.LogInformation(
"浏览器拦截,浏览器:{Browser},版本:{Version}",
detectionService.Browser.Name,
detectionService.Browser.Version
);
// 记录IP拦截
await rateLimitService.RecordBlockAsync(clientIp, "低版本浏览器");
await Response403Async(httpContext);
return;
}
// 3. 检查是否匹配黑名单
// 获取黑名单数据 (延迟获取,只有通过前面规则检查的才需要查询黑名单)
List<VmBlock> blocks = [];
try
{
blocks = await blockService.GetBlocksAsync(30);
}
catch (OperationCanceledException) when (httpContext.RequestAborted.IsCancellationRequested)
{
return;
}
catch (Exception e)
{
logger.LogError(e, "获取黑名单失败");
}
// 如果命中黑名单,下一步,并且记录
if (await CheckBlockListAsync(blockService, httpContext, blocks))
{
logger.LogInformation(
"黑名单拦截,RequestMethod:{RequestMethod}, RequestPath:{RequestPath}",
httpContext.Request.Method,
httpContext.Request.Path
);
// 记录IP拦截
await rateLimitService.RecordBlockAsync(clientIp, "黑名单");
await SaveBlockAndResponse403Async(blockService, httpContext);
return;
}
await next.Invoke(httpContext);
// 4. 记录404请求
// 如果404,如果在忽略列表内,忽略下一步,不在忽略列表内,记录
// (注:由于前面已经对忽略列表进行了检查并return,所以理论上这里只处理非忽略列表的请求)
await Record404NotFoundAsync(blockService, httpContext, requestPath, ignoreRequestPrefixes);
}
/// <summary>
/// 检查是否匹配黑名单
/// </summary>
private async Task<bool> CheckBlockListAsync(
IBlockService blockService,
HttpContext httpContext,
List<VmBlock> blocks
)
{
var requestPath = httpContext.Request.Path.Value ?? "";
if (string.IsNullOrWhiteSpace(requestPath))
{
return false;
}
// 查找匹配的黑名单项(不区分大小写)
var matchedBlock = blocks.FirstOrDefault(x =>
string.Equals(x.RequestPath, requestPath, StringComparison.OrdinalIgnoreCase)
);
if (matchedBlock != null)
{
// 增加访问计数
await blockService.IncrementCountAsync(
matchedBlock.RequestMethod,
matchedBlock.RequestPath
);
logger.LogInformation("黑名单拦截: {RequestPath}", requestPath);
return true;
}
return false;
}
/// <summary>
/// 保存黑名单并返回403响应
/// </summary>
private async Task SaveBlockAndResponse403Async(
IBlockService blockService,
HttpContext httpContext
)
{
var requestPath = httpContext.Request.Path.Value ?? "";
if (string.IsNullOrWhiteSpace(requestPath) || requestPath == "/")
{
return;
}
var ipAddress = httpContext
.Request.GetIpAddress()
.Split(',')
.Where(x => !string.IsNullOrEmpty(x))
.Select(x => x.Trim())
.ToArray();
var userAgent = httpContext.Request.Headers.UserAgent.ToString();
var method = httpContext.Request.Method.ToUpper();
var block = new Block
{
RequestMethod = method,
RequestPath = requestPath,
IpAddresses = ipAddress,
UserAgents = [userAgent],
};
// 先保存黑名单记录
await blockService.SaveBlockAsync(block);
// 再尝试返回403响应(内部会检查响应状态)
await Response403Async(httpContext);
}
/// <summary>
/// 记录404请求
/// </summary>
private async Task Record404NotFoundAsync(
IBlockService blockService,
HttpContext httpContext,
string requestPath,
List<string> ignoreRequestPrefixes
)
{
if (httpContext.Response.StatusCode != 404)
{
return;
}
if (string.IsNullOrWhiteSpace(requestPath) || requestPath == "/")
{
return;
}
if (IsUuidNoiseRequest(httpContext, requestPath))
{
return;
}
// 检查是否在忽略列表中
if (
ignoreRequestPrefixes.Any(x =>
requestPath.StartsWith(x, StringComparison.OrdinalIgnoreCase)
)
)
{
return;
}
var ipAddress = httpContext
.Request.GetIpAddress()
.Split(',')
.Where(x => !string.IsNullOrEmpty(x))
.Select(x => x.Trim())
.ToArray();
var userAgent = httpContext.Request.Headers.UserAgent.ToString();
var method = httpContext.Request.Method.ToUpper();
var block = new Block
{
RequestMethod = method,
RequestPath = requestPath,
IpAddresses = ipAddress,
UserAgents = [userAgent],
};
await blockService.SaveBlockAsync(block);
}
/// <summary>
/// 过滤前端 blob/hls 等场景产生的 /{UUID} 噪声请求,避免污染黑名单。
/// </summary>
private static bool IsUuidNoiseRequest(HttpContext httpContext, string requestPath)
{
if (
!HttpMethods.IsGet(httpContext.Request.Method)
&& !HttpMethods.IsHead(httpContext.Request.Method)
)
{
return false;
}
var segment = requestPath.Trim('/');
if (string.IsNullOrWhiteSpace(segment) || segment.Contains('/'))
{
return false;
}
return Guid.TryParseExact(segment, "D", out _)
|| Guid.TryParseExact(segment, "N", out _)
|| ObjectId.TryParse(segment, out _);
}
/// <summary>
/// 返回403响应
/// </summary>
private async Task Response403Async(HttpContext httpContext)
{
// 检查响应是否已经开始,避免 "response has already started" 错误
if (httpContext.Response.HasStarted)
{
logger.LogWarning(
"尝试返回403响应时发现响应已开始,IP: {ClientIp}, 路径: {RequestPath}",
httpContext.Request.GetIpAddress(),
httpContext.Request.Path
);
return;
}
httpContext.Response.StatusCode = 403;
httpContext.Response.ContentType = "text/html";
await httpContext.Response.SendFileAsync(
Path.Combine(webHostEnvironment.WebRootPath, "reject.html")
);
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
这是一个ASP.NET Core中间件,名为RejectBotsMiddleware,主要用于识别和拦截恶意请求、机器人访问以及维护黑名单系统。让我详细解释其功能和工作流程:
主要功能
1. 多层防护机制
- 自定义拦截规则检查
- 浏览器版本检查
- 黑名单匹配
- 404请求记录
2. 智能过滤策略
详细工作流程
第0步:用户身份检查
if (httpContext.User.Authenticated || webHostEnvironment.IsDevelopment())
{
await next.Invoke(httpContext);
return;
}
- 已登录用户直接放行
- 开发环境下直接放行
第1步:忽略列表检查
if (ignoreRequestPrefixes.Any(x =>
requestPath.StartsWith(x, StringComparison.OrdinalIgnoreCase)))
{
await next.Invoke(httpContext);
return;
}
- 检查请求路径是否在配置的忽略列表中
- 如果在忽略列表中,直接放行
第2步:自定义拦截规则检查
- 检查是否命中自定义拦截规则
- 拦截空User-Agent请求
- 拦截低版本浏览器(Chrome/Firefox/Edge < 100.0.0.0,IE浏览器)
第3步:黑名单检查
var matchedBlock = blocks.FirstOrDefault(x =>
string.Equals(x.RequestPath, requestPath, StringComparison.OrdinalIgnoreCase));
- 延迟加载黑名单数据(只有通过前面检查的请求才查询)
- 匹配黑名单中的请求路径
第4步:404请求记录
- 记录404状态的请求到黑名单
- 过滤UUID噪声请求(如前端blob/hls产生的随机请求)
核心辅助方法
SaveBlockAndResponse403Async
- 保存恶意请求信息到黑名单
- 返回403 Forbidden响应
IsUuidNoiseRequest
return Guid.TryParseExact(segment, "D", out _)
|| Guid.TryParseExact(segment, "N", out _)
|| ObjectId.TryParse(segment, out _);
- 智能识别前端产生的UUID格式噪声请求
- 避免这些请求污染黑名单
Response403Async
- 安全地返回403响应
- 检查响应是否已开始,避免异常
- 返回自定义的reject.html页面
设计亮点
- 分层防护:从用户认证 → 忽略列表 → 规则检查 → 黑名单 → 404记录
- 性能优化:延迟加载黑名单,只对需要的请求进行数据库查询
- 智能过滤:自动识别并过滤前端产生的噪声请求
- 详细日志:每个拦截环节都有相应的日志记录
- 安全响应:检查响应状态,避免重复设置响应头
这个中间件是一个完整的Web应用安全防护解决方案,能够有效防止恶意访问、机器人攻击,并维护一个动态的黑名单系统。
评论加载中...