using Dpz.Core.Public.Entity;
using Wangkanai.Detection.Models;
using Wangkanai.Detection.Services;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace Dpz.Core.Web.Library.Middleware;
public class RejectBots(
RequestDelegate next,
IConfiguration configuration,
IWebHostEnvironment webHostEnvironment
)
{
private readonly Dictionary<string, object> _attributes = new();
public async Task Invoke(
HttpContext httpContext,
ILoggerFactory loggerFactory,
IBlockService blockService,
IDetectionService detectionService
)
{
var logger = loggerFactory.CreateLogger<RejectBots>();
var requestIpAddress = httpContext.Request.GetIpAddress();
List<VmBlock> blocks = [];
try
{
blocks = await blockService.GetBlocksAsync();
}
catch (Exception e)
{
logger.LogError(e, "get blocks fail");
}
if (
(
detectionService.Browser.Name is Browser.Chrome or Browser.Firefox
&& detectionService.Browser.Version < new Version("100.0.0.0")
)
|| detectionService.Browser.Name is Browser.InternetExplorer
)
{
await Response403Async(httpContext, logger, requestIpAddress);
return;
}
var bots = configuration.GetSection("RejectBots").Get<List<RejectBot>>() ?? [];
var ignoreRequestPrefixes =
configuration.GetSection("IgnoreRequestPrefix").Get<List<string>>() ?? [];
if (IsBot(httpContext, bots, requestIpAddress, blocks))
{
await Response403Async(httpContext, logger, requestIpAddress);
return;
}
await next.Invoke(httpContext);
await Record404NotFoundAsync(blockService, httpContext, ignoreRequestPrefixes, blocks);
}
private bool IsBot(
HttpContext httpContext,
List<RejectBot> bots,
string requestIpAddress,
List<VmBlock> blocks
)
{
var blockAccessThreshold = configuration.GetValue("BlockAccessThreshold", 0);
var currentBlock = blocks.Find(x =>
x.AccessCount >= blockAccessThreshold
&& (
x.UserAgents.Contains(
httpContext.Request.Headers.UserAgent.ToString(),
new IgnoreCase()
)
|| x.IpAddresses.Contains(requestIpAddress, new IgnoreCase())
|| string.Equals(httpContext.Request.Path, x.RequestPath)
)
);
if (currentBlock != null)
{
_attributes.TryAdd("@Block", currentBlock);
return true;
}
return bots.Any(x =>
{
if (!string.IsNullOrEmpty(x.UserAgent) && string.IsNullOrEmpty(x.IpAddress))
{
return httpContext.Request.Headers.UserAgent == x.UserAgent;
}
if (string.IsNullOrEmpty(x.UserAgent) && !string.IsNullOrEmpty(x.IpAddress))
{
return requestIpAddress == x.IpAddress;
}
if (!string.IsNullOrEmpty(x.UserAgent) && !string.IsNullOrEmpty(x.IpAddress))
{
return httpContext.Request.Headers.UserAgent == x.UserAgent
&& requestIpAddress == x.IpAddress;
}
return false;
});
}
private async Task Record404NotFoundAsync(
IBlockService blockService,
HttpContext httpContext,
List<string> ignoreRequestPrefixes,
List<VmBlock> blocks
)
{
if (
httpContext.Response.StatusCode == 404
&& !ignoreRequestPrefixes.Any(x =>
httpContext
.Request.Path.ToString()
.StartsWith(x, StringComparison.OrdinalIgnoreCase)
)
)
{
var ipAddress = httpContext
.Request.GetIpAddress()
.Split(',')
.Where(x => !string.IsNullOrEmpty(x))
.Select(x => x.Trim())
.ToArray();
var userAgent = httpContext.Request.Headers.UserAgent;
var method = httpContext.Request.Method.ToUpper();
var requestPath = httpContext.Request.Path;
if (
blocks.Any(x =>
string.Equals(x.RequestMethod, method, StringComparison.OrdinalIgnoreCase)
&& string.Equals(x.RequestPath, requestPath, StringComparison.OrdinalIgnoreCase)
&& x.UserAgents.Any(y =>
string.Equals(y, userAgent, StringComparison.OrdinalIgnoreCase)
)
&& x.IpAddresses.Except(ipAddress).Any()
)
)
{
await blockService.IncrementCountAsync(method, requestPath);
return;
}
var block = new Block
{
RequestMethod = method,
RequestPath = requestPath,
IpAddresses = ipAddress,
UserAgents = userAgent
};
await blockService.SaveBlockAsync(block);
}
}
private async Task Response403Async(HttpContext httpContext, ILogger logger, string ipAddress)
{
//var logger = loggerFactory.CreateLogger("Dpz.Core.Web.Library.Middleware.RejectBots");
httpContext.Response.StatusCode = 403;
httpContext.Response.ContentType = "text/html";
await httpContext.Response.SendFileAsync(
Path.Combine(webHostEnvironment.WebRootPath, "reject.html")
);
var tags = new Dictionary<string, object>
{
{ "IpAddress", ipAddress },
{ "UserAgent", httpContext.Request.Headers.UserAgent },
{ "RequestHost", httpContext.Request.Host },
{ "RequestScheme", httpContext.Request.Scheme },
{ "RequestMethod", httpContext.Request.Method },
{ "RequestPath", httpContext.Request.Path },
{ "RequestQueryString", httpContext.Request.QueryString },
};
foreach (var item in _attributes)
{
tags.TryAdd(item.Key, item.Value);
}
using (logger.BeginScope(tags))
{
logger.LogInformation("被拦截机器人");
}
}
private class RejectBot
{
public string? UserAgent { get; set; }
public string? IpAddress { get; set; }
}
private class IgnoreCase : IEqualityComparer<string>
{
public bool Equals(string? x, string? y)
{
if (x is null && y is null)
return true;
if (x is null || y is null)
return false;
return string.Equals(x, y, StringComparison.CurrentCultureIgnoreCase);
}
public int GetHashCode(string obj)
{
var result = "IgnoreCase".Sum(x => x);
return obj.ToLower().GetHashCode() + result;
}
}
}