# 思路记录访问服务器的 IP,当一定时间段内访问次数或错误次数超过一定限度时,将请求直接返回 403 不经过逻辑代码
# 代码1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 using Microsoft.AspNetCore.Http;using Newtonsoft.Json;using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Net;using System.Text;using System.Threading;using System.Threading.Tasks;namespace Helper { public class DefenceCCMiddleware { private readonly RequestDelegate _next; public DefenceCCMiddleware (RequestDelegate next ) { _next = next; } public async Task Invoke (HttpContext context ) { string ip = GetRequestIP(context); if (BlackList.Contains(ip)) { context.Response.StatusCode = (int )HttpStatusCode.Forbidden; context.Response.ContentLength = 0 ; return ; } SaveError(context); await _next.Invoke(context); } public class ResponseError { public string IpAddress { get ; set ; } public DateTime OccurTime { get ; set ; } = DateTime.Now; public ResponseError () { new Thread(() => { Thread.Sleep(60 * 1000 ); RemoveError(IpAddress); }).Start(); } } private static List<ResponseError> ErrorSave = new (); public static List<string > BlackList = new (); public bool SaveError (HttpContext context ) { string ip = GenericHelper.GetRequestIP(context); int secondReq = 0 , minuteReq = 0 , hourReq = 0 ; foreach (var item in ErrorSave) { if (item.IpAddress == ip) { if (item.OccurTime.AddSeconds(1 ) > DateTime.Now) { secondReq++; } else if (item.OccurTime.AddMinutes(1 ) > DateTime.Now) { minuteReq++; } else if (item.OccurTime.AddHours(1 ) > DateTime.Now) { hourReq++; } } } int maxSecond = ConfigHelper.GetConfig<int >("MaxRequestPerSecond" ); int maxminute = ConfigHelper.GetConfig<int >("MaxRequestPerMinute" ); if (secondReq > maxSecond || minuteReq > maxminute) { BlackList.Add(ip); new Thread(() => { Thread.Sleep(60 * 60 * 1000 ); BlackList.Remove(ip); }).Start(); return true ; } else { ErrorSave.Add(new ResponseError { IpAddress = ip }); } return false ; } public static void RemoveError (string ip ) { ErrorSave.Remove(ErrorSave.First(x => x.IpAddress == ip)); } public static string GetRequestIP (HttpContext context ) { var ip = context.Request.Headers["X-Forwarded-For" ].FirstOrDefault(); if (ip == null ) { return context.Connection.RemoteIpAddress.ToString(); } if (ip.Contains("," )) { ip = ip.Split(',' ).First().Trim(); } return ip; } } }
# 解释由于服务器使用了 nginx,常规获取 IP 的方法将只能获取到 localhost,后续章节讲如何配置 将请求的 IP 与请求时间存在数组中,每次请求时统计数组中的该 IP 在一秒钟内、一分钟内、一小时内的请求数量,如果有超过限额的 IP 就将此 IP 放置于黑名单中,再次碰到此 IP 将直接返回 403 而不经过逻辑代码 写法并不完善,对于每个请求都将开启一个线程用于定时将 IP 从数组中移除,对于大型网站而言可能会造成性能损失与负载增加。想要改进可以使用一整个线程对整个数组进行维护 # 配置 Nginx 转发 IP 请求在 nginx 的 location 中添加以下配置 1 2 3 4 proxy_set_header Host $host ;proxy_set_header X-Real-IP $remote_addr ;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for ;proxy_set_header X-Forwarded-Host $server_name ;
这样就会真实 IP 写在请求头内传递给后端
Asp.net 安装 Microsoft.AspNetCore.HttpOverrides 包 在 Startup.cs 的 ConfigureServices 函数中添加以下代码 1 2 3 4 5 6 7 services.Configure<ForwardedHeadersOptions>(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; options.KnownNetworks.Clear(); options.KnownProxies.Clear(); });