Webman实现IP黑名单必须穿透代理获取真实IP:Nginx需配置set_real_ip_from与real_ip_header X-Forwarded-For,PHP层优先读X-Real-IP,再解析X-Forwarded-For并过滤私有地址,黑名单查用Redis或APCu毫秒级响应,中间件须全局注册、排除静态资源且正确放行。

Webman 实现 IP 黑名单,不能只靠 in_array() 判断 $_SERVER['REMOTE_ADDR']——这个值在 CDN、Cloudflare、Nginx 反代后基本不可信;必须穿透代理拿到真实 IP,并用内存或 Redis 快速查黑名单,否则中间件本身就成了性能瓶颈。
怎么拿到真实的客户端 IP
直接读 $_SERVER['REMOTE_ADDR'] 在上线后大概率失效。真实场景中,它常是 127.0.0.1 或 CDN 出口 IP。
- Nginx 必须配置
set_real_ip_from+real_ip_header X-Forwarded-For,白名单只填你可控的代理网段(如10.0.0.0/8) - PHP 层别无脑信任
$_SERVER['HTTP_X_FORWARDED_FOR'],它可被伪造;要先验证来源可信,再取最左非私有 IP - 推荐封装一个
get_client_ip(true)函数:优先从$request->header('x-real-ip')取, fallback 到解析X-Forwarded-For并过滤私有地址(127.0.0.0/8、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、::1) - IPv6 地址必须用
inet_pton()+inet_ntop()处理,ip2long()会返回 false
黑名单查得慢,中间件就成攻击入口
每次请求都 file_get_contents('blacklist.txt') 或查数据库,QPS 上千时 DB/磁盘直接拖垮服务。
- 静态小名单('ip_blacklist',用
apcu_fetch()查,避免重复加载 - 动态大名单(万级+):用 Redis
SET存 IP,中间件里调$redis->sismember('blacklist:ips', $ip),毫秒级响应 - 绝对不要在中间件里写
Db::table('blacklist')->where('ip', $ip)->count()—— 这是典型反模式 - 文件黑名单热更新?用
filemtime()检查修改时间,变化时才重载到 APCu,别每请求都 stat
中间件挂载位置和写法必须严格合规
挂错位置或签名不对,中间件就静默失效——请求照进控制器,黑名单形同虚设。
立即学习“PHP免费学习笔记(深入)”;
- 必须注册在
config/middleware.php的''键下(全局中间件),才能拦住所有非法请求 - 中间件类必须实现
Webman\MiddlewareInterface,process()方法签名固定为process($request, $handler) - 别漏掉
return $handler($request)放行,也别用die或exit终止——要返回response()->withStatus(403) - 务必排除静态资源:
if (str_starts_with($request->getUri()->getPath(), '/static/')) return $handler($request);,否则 JS/CSS 加载失败
为什么封禁后请求还在进?检查这三点
常见“明明加了黑名单却拦不住”的问题,往往卡在这几个隐性环节。
- 中间件没放在全局(
''键),而是只挂到某个路由组,导致未匹配路由的请求绕过 - IP 校验逻辑写在
handle()里,但 Webman 要求权限类拦截必须用preHandle()返回false才彻底中断链 - Redis 或 APCu 缓存没生效:确认 PHP 进程有 APCu 扩展,Redis 连接未超时,且 key 命名一致(比如用了
blacklist:ip却查blacklist_ips)
真实线上环境里,IP 黑名单从来不是单点开关——它必须和限流、Token 校验、日志审计联动,且所有环节都要考虑代理穿透、IPv6 兼容、缓存一致性。少一个,攻击者就能绕过。



















