白名单必须放在全局中间件里,因为ThinkPHP请求生命周期为“接收→全局中间件→路由→控制器”,路由文件中判断不会执行;需用$request->realIP()获取真实IP,前缀匹配URL,配置化管理白名单规则并支持CIDR网段校验。

白名单必须放在全局中间件里,路由文件里写的判断根本不会执行。
为什么中间件是唯一生效位置
ThinkPHP 请求生命周期固定为:接收请求 → 执行全局中间件 → 匹配路由 → 调用控制器。你在 route/api.php 里写 if ($request->ip() !== '192.168.1.100') die('forbidden'),这段代码永远不运行——因为路由匹配前,中间件已经决定放不放行;路由层只负责分发,不参与权限拦截。
白名单逻辑若晚于控制器执行,就等于把鉴权后置,攻击者可直接触发数据库查询、日志写入甚至敏感操作。
- 中间件注册必须写在
app/middleware.php的数组最前面 - 别误配到
route/app.php或控制器里,那只是无效代码 - 测试时用
curl -H "X-Real-IP: 1.2.3.4" http://your-api.com/admin/info模拟真实场景,别只靠127.0.0.1
怎么安全获取真实客户端 IP
$request->ip() 默认只读 $_SERVER['REMOTE_ADDR'],Nginx 反代后它永远是 127.0.0.1;硬取 $_SERVER['X-Forwarded-For'] 则极易被伪造,形同虚设。
立即学习“PHP免费学习笔记(深入)”;
正确做法是开启可信代理配置:'trust_proxy' => ['127.0.0.1'](写在 config/app.php),然后统一用 $request->realIP() ——TP 6.1+ 内部已校验头来源是否可信。
- 没开
trust_proxy就调realIP(),会退化成ip(),依然不准 - 本地开发时 Nginx 和 PHP 同机部署,
127.0.0.1是唯一可信代理 IP - 生产环境若用 CDN 或多层 LB,需把最外层出口 IP 加进
trust_proxy数组
白名单规则该用什么格式匹配
别用 in_array($url, $whitelist) 查完整 URL ——带参数的 /api/user?id=1 和 /api/user 就算两个不同字符串,维护爆炸且漏匹配。
前缀匹配才是正解:str_starts_with($url, '/admin/')(PHP 8.0+)或 strpos($url, '/admin/') === 0。支持 /admin/、/api/v1/ 这类路径范围,语义清晰、扩展性强。
- 白名单规则建议从配置文件读取,比如
config/route.php里定义'admin_whitelist_prefixes' => ['/admin/', '/api/admin/'] - 避免硬编码在中间件里,上线后改配置不用重部署
- 别用正则匹配路径,点号、斜杠转义易错,性能还差
怎么支持 CIDR 网段和单 IP 混合校验
白名单数组里混写 ['192.168.1.100', '10.0.0.0/8', '2001:db8::/32'] 很常见,但 PHP 原生不支持 CIDR 判断,得自己补。
核心逻辑是:无 / 的当精确 IP 比对;有 / 的拆出子网和掩码位,用 ip2long() + 位运算校验是否落在网段内(IPv4);IPv6 需额外处理,建议用 filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) 分流。
- 别直接
in_array($clientIp, $whitelist),它对10.0.0.5/8这种完全无效 - 网段匹配函数必须兼容 IPv4 和 IPv6,否则线上突然出现 IPv6 访问就被拦死
- 测试时务必覆盖
192.168.1.1(精确匹配)、192.168.1.100(/24 网段内)、192.168.2.1(/24 网段外)三种 case
真正容易被忽略的不是怎么写逻辑,而是信任边界——trust_proxy 配错、CIDR 判定漏 IPv6、白名单路径前缀漏掉重写后的实际 URL,这三个点任何一个出问题,白名单就只剩心理安慰。
文章来自机圈观察员网,发布者:,转载请注明出处:https://www.jqgcy.com/xitongjiaocheng/123893.html