最直接方式是自定义ipfilterattribute继承actionfilterattribute,在onactionexecuting中校验remoteipaddress,配合useforwardedheaders处理代理场景,并用ioptionsmonitor支持cidr与热更新。

直接在 ActionFilterAttribute 里做 IP 白名单校验,是最轻量、最可控的方式。它不依赖中间件顺序,不污染 Controller,且能在业务逻辑执行前就拒绝非法请求。
OnActionExecuting 中怎么取真实客户端 IP
别直接用 HttpContext.Connection.RemoteIpAddress —— 在 Nginx、Cloudflare 或 Azure Load Balancer 后面时,它返回的是代理内网地址,白名单立刻失效。
- 必须先调用
app.UseForwardedHeaders(),并在Program.cs中配置可信源:options.KnownProxies = new[] { IPAddress.Parse("10.0.10.10") }或options.KnownNetworks = new[] { IPNetwork.Parse("10.0.0.0/8") } - Cloudflare 用户要手动加它的官方 IP 段,比如
"173.245.48.0/20",否则仍取到节点 IP - 启用后,
RemoteIpAddress就自动变成真实客户端 IP,无需自己解析X-Forwarded-For - 开发环境(Kestrel 直连)不需要 KnownProxies,但代码要兼容——不要写
if (env.IsDevelopment())绕过配置
白名单存储与匹配为什么不能用 List.Contains
用 List<string></string> 存 IP 字符串再调 .Contains(),每次都是 O(n) 全遍历;更糟的是,它无法支持 CIDR 网段(如 "192.168.1.0/24"),也搞不定 IPv4 映射 IPv6 地址(如 "::ffff:192.168.1.100")。
- IP 列表建议用
HashSet<ipaddress></ipaddress>存解析后的IPAddress实例,查找是 O(1) - CIDR 网段必须用专门库解析,推荐 NuGet 包
IPNetwork2,调IPNetwork.Parse("10.0.0.0/16")得到可复用的只读实例 - 匹配时用
network.Contains(clientIp),注意传入的clientIp要先调.MapToIPv4()处理映射地址 - 别在
OnActionExecuting里每次调IPNetwork.Parse——提前在IOptionsMonitor<ipfilteroptions></ipfilteroptions>初始化时完成解析
如何让白名单支持热更新且线程安全
硬编码或从 appsettings.json 读一次就缓存,改配置得重启服务,生产环境不可接受。
- 把白名单定义为
IOptionsMonitor<ipfilteroptions></ipfilteroptions>,其中IPFilterOptions包含IReadOnlyList<ipnetwork></ipnetwork>和开关字段IsEnabled - 配置变更时,
IOptionsMonitor自动触发回调,重新构建HashSet<ipnetwork></ipnetwork>或刷新内存缓存 -
IPNetwork实例本身是只读的,多个线程共享安全;但集合容器(如ConcurrentDictionary)若需写入,要用线程安全类型 - 避免在过滤逻辑里访问数据库或远程配置中心——延迟高、易失败,应由后台任务定期拉取并更新本地
IOptionsMonitor
真正容易被忽略的点是:IPv4 映射 IPv6 地址的归一化处理。本地调试时 HttpClient 发请求常走 ::1 或 ::ffff:127.0.0.1,如果没调 .MapToIPv4() 就直接比对,白名单永远不生效。











