ThinkPHP中间件中request()->ip()不可靠,需先调用setTrustedProxies()配置可信代理段,再用ip(true)解析X-Real-IP/X-Forwarded-For;否则返回代理内网IP或0.0.0.0。

中间件里直接调用 request()->ip() 不可靠
ThinkPHP 中间件执行时,request() 对象虽已初始化,但默认的 ip() 方法只读 $_SERVER['REMOTE_ADDR'],在 Nginx + PHP-FPM 或使用 SLB/CDN 的部署下,这个值往往是代理或负载均衡器的内网 IP,不是真实客户端 IP。
常见错误现象:本地开发环境返回正确 IP,上线后全变成 10.0.0.x 或 172.16.x.x;或者 request()->ip() 返回空字符串或 0.0.0.0。
- 根本原因是框架未自动识别可信代理头(如
X-Real-IP、X-Forwarded-For) -
request()->ip()默认不开启高级模式,也不校验代理链合法性 - 中间件中不能直接依赖
get_client_ip()函数——它在早期 ThinkPHP 版本中是全局函数,但 TP6+ 已移除该函数,需手动引入或重写
TP6+ 中间件获取真实 IP 的标准做法
必须显式配置可信代理,并使用 Request::instance()->ip() 配合参数启用代理解析。关键点不是“怎么调”,而是“怎么配”。
在 app/middleware.php 或中间件类中,先确保请求对象已加载可信代理段:
立即学习“PHP免费学习笔记(深入)”;
// 示例:在中间件 __invoke 方法开头 use think\Request; $request = Request::instance(); // 强制设置可信代理(根据实际部署填写,不可填 0.0.0.0/0) $request->setTrustedProxies(['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', '127.0.0.1']); // 然后才能安全调用 ip() 并启用代理解析 $ip = $request->ip(true); // true 表示启用 X-Forwarded-For 解析
-
$request->ip(true)才会检查X-Real-IP和X-Forwarded-For;不传true等价于只读REMOTE_ADDR -
setTrustedProxies()必须早于ip()调用,且 IP 段必须严格匹配你实际的代理出口(如阿里云 SLB 回源段、K8s Service CIDR) - 若用 Nginx,确认已配置
proxy_set_header X-Real-IP $remote_addr;和proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
自定义中间件封装真实 IP 获取逻辑
避免每个中间件重复写 setTrustedProxies 和判断逻辑,建议封装成可复用的方法。注意:不要直接复用旧版 get_client_ip() 函数,它没做代理链长度限制,易被伪造。
推荐轻量封装(放在 app/common.php 或中间件基类中):
function get_real_client_ip(): string
{
$request = \think\Request::instance();
// 仅当已配置可信代理时才启用代理解析
if (empty($request->getTrustedProxies())) {
return $request->ip();
}
$ip = $request->ip(true);
// 简单过滤:拒绝以逗号分隔的多 IP(防 XFF 伪造)
if (strpos($ip, ',') !== false) {
$ips = array_map('trim', explode(',', $ip));
$ip = $ips[0] ?? '0.0.0.0';
}
return filter_var($ip, FILTER_VALIDATE_IP) ? $ip : '0.0.0.0';
}
- 该函数在中间件中可直接调用:
$clientIp = get_real_client_ip(); - 关键防护点:检查
X-Forwarded-For是否含逗号,只取第一个(最左),因为只有最左才是最初发起请求的客户端 IP - 务必配合 Nginx 的
real_ip_header X-Real-IP;和set_real_ip_from指令,否则ip(true)仍可能失效
TP5.1 与 TP6 的行为差异必须注意
TP5.1 的 request()->ip() 默认不支持 true 参数,也没有 setTrustedProxies 方法;TP6 则统一了这一接口。混用版本会导致中间件行为不一致。
- TP5.1 中间件只能手动解析头:
$request->header('x-real-ip', $request->header('x-forwarded-for', $request->ip())),但无校验 - TP6 中若跳过
setTrustedProxies()直接调ip(true),框架会静默忽略代理头,仍返回REMOTE_ADDR - TP6.3+ 增加了
Request::setTrustedHeaderName(),可自定义头名(如用X-Cluster-Client-IP),但默认仍认X-Forwarded-For
真实部署中最容易被忽略的是:代理段配置写错一个数字,或 Nginx 缺少 set_real_ip_from,结果中间件拿到的永远是假 IP,而日志和风控系统却信以为真。



















