在nginx代理模式下,java需通过解析x-forwarded-for等http头获取客户端真实ip,而非直接用request.getremoteaddr(),因后者仅返回代理地址;代码中应按ip_headers顺序读取并校验头部,取第一个有效ip。

RESTful 接口里直接用 $request->userIP 或 $request->userHostAddress 拿到的几乎肯定是错的——尤其在 Nginx、SLB、CDN 后面部署时,实际拿到的是最后一跳代理的 IP,不是用户真实出口 IP。
Yii2 RESTful 中为什么 $request->userIP 总是 127.0.0.1 或内网地址
因为 Yii2 的 userIP 属性默认只读 $_SERVER['REMOTE_ADDR'],而反向代理(如 Nginx)转发请求后,这个值就变成了代理机器自己的地址。RESTful 接口通常走 API 网关或负载均衡,天然处于多层代理链中。
关键点在于:getUserIP() 方法才是设计用来处理代理头的,但必须配合配置才生效:
-
ipHeaders必须显式声明你信任哪些 HTTP 头(如X-Forwarded-For、X-Real-IP),否则getUserIP()完全忽略它们 -
trustedHosts必须填你实际的代理网段(如10.0.0.0/8、192.168.0.0/16),否则 Yii 认为这些头不可信,直接丢弃 - 云厂商头名不统一:腾讯云用
Tencent-Cloud-Real-IP,阿里云 SLB 默认用X-Forwarded-For但可能被覆盖,需查文档确认并加入ipHeaders - 别把公网 IP 加进
trustedHosts,否则攻击者伪造X-Forwarded-For就能冒充任意 IP
Yii2 RESTful 接口正确获取真实 IP 的写法
不要在控制器里硬写 $request->userIP,改用 $request->getUserIP(),并在 config/web.php 的 request 组件中补全配置:
'request' => [
'class' => 'yii\web\Request',
'enableCsrfValidation' => false,
'ipHeaders' => ['X-Forwarded-For', 'X-Real-IP', 'Tencent-Cloud-Real-IP'],
'trustedHosts' => ['10.0.0.0/8', '192.168.0.0/16', '172.16.0.0/12'],
],
然后在 REST 控制器中:
public function actionIndex()
{
$ip = \Yii::$app->request->getUserIP();
// 不要再用 $this->request->userIP
}
注意:getUserIP() 返回字符串,可能为 '::1'(IPv6 本地回环),需按需过滤;若返回空,说明所有头都未命中或代理不在 trustedHosts 中。
Yii3 RESTful 接口取 IP 必须手动解析头
Yii3 没有 getUserIP(),也没有 trustedHosts 配置,一切靠自己。PSR-7 的 ServerRequestInterface 只提供原始头和服务器参数:
- 先读头:
$request->getHeaderLine('X-Forwarded-For'),注意它返回逗号分隔的 IP 列表(如"203.0.113.195, 198.51.100.1"),真实客户端 IP 是第一个 - 再 fallback:
$request->getServerParams()['remote_addr'],这是最底层连接 IP - 必须自己校验来源是否可信:从
$request->getServerParams()['remote_addr']提取 IP,判断是否属于你认可的代理网段(比如用ip_in_range()手动实现) - 如果来源 IP 不在白名单里,就不能信任
X-Forwarded-For,否则会被伪造绕过 -
User-Agent用$request->getHeaderLine('User-Agent')即可,但要注意它可能为空字符串,不是null
容易被忽略的细节和线上典型错误
很多团队上线后才发现 IP 日志全是 127.0.0.1 或固定内网段,原因往往不是代码写错,而是环境理解偏差:
- Nginx 配置没透传头:检查是否漏了
proxy_set_header X-Real-IP $remote_addr;和proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - Docker 或 K8s Service 导致
remote_addr变成集群内部地址,此时trustedHosts要包含 CNI 网段(如10.244.0.0/16) - 移动端 WebView 或小程序发起请求时,UA 可能极长(超 4KB),存数据库前不截断会导致 MySQL 报
Packet too large - 日志里 IP 字段定义为
VARCHAR(15)—— 这只能存 IPv4,IPv6 地址如2001:db8::1直接被截断,应改用INET6类型或TEXT
真实 IP 获取不是“调一个方法就行”的事,它横跨 Nginx 配置、云厂商文档、框架配置、数据库设计四个层面,任一环节掉链子,整条链就失效。











