获取用户的真实 ip,对于安全业务来说非常重要。
阿里云场景下一个http 请求一般为:
用户ip --> ddos 高防 ip ->slb ip
对于web服务器来说,主要是通过两种方式获取 ip
与服务器建立tcp连接的地址 remote address
通过 http header 的 x-forwarded-for 字段
对应的 php 变量如下
与服务器建立tcp连接的 ip
无法伪造,很合适作为用户真实ip
但是 http 请求经过七层代理后,就不是用户ip了,一般为slb ip
通过http header传递给服务端
可以伪造,有可能获取的数据不准确,还可能引发 xss,sql 注入等问题
x-forwarded-for :格式如下
x-forwarded-for: client, proxy1, proxy2
如果一个 http 请求到达服务器之前,经过了三个代理 proxy1、proxy2、proxy3,ip 分别为 ip1、ip2、ip3,用户真实 ip 为 ip0,服务端最终会收到以下信息:
x-forwarded-for: ip0, ip1, ip2
proxy3 直连服务器,它会给 xff 追加 ip2,表示它是在帮 proxy2 转发请求。列表中并没有 ip3,ip3 可以在服务端通过 remote_addr 字段获得。
如果用户篡改 x-forwarded-for
x-forwarded-for: ip100, ip0, ip1, ip2
我们怎么通过这两个字段获取用户真实 ip 呢?
如果 http 请求没有经过七层代理,直接读取 remote_addr 字段最好
如果 http 请求经过七层代理, 我们只能从 x-forwarded-for 获取,但是我们必须过滤用户伪造数据,获取真实的用户 ip
如上面例子 ip1, ip2是我们自己配置的 ip,可信任的ip,过滤掉可信任的代理ip,最后一个不可信任的ip就是用户ip。
要解决的问题如下
nginx的访问日志,记录用户真实ip,现在记录的是remote address。
php获取用户真实ip,如果我们获取x-forwarded-for第一个ip。容易被用户篡改。
调研过程中,发现ngx_http_realip_module 满足我们需求, 当获取了用户真实ip,会赋值给 nginx变量 $remote_addr 变量,那么 php的 $_server['remote_addr'] 也变成了用户真实ip。
与web服务器建议tcp连接的ip 存储在变量 $realip_remote_addr 中。
set_ip_from
设置信任 ip,就是我们自己的代理ip,可以设置多个
real_ip_header
设置请求头字段,表示从x-forwarded-for字段获取用户 ip
real_ip_recursive
off:如果remote address 能够匹配set_real_ip_from 的ip,用户 x-forwarded-for最后一个 ip 作为用户ip。
on: 如果remote address 能够匹配set_real_ip_from 的ip,用户 x-forwarded-for最后一个不可信任 ip 作为用户ip。
我们的配置如下,注意还没有上线
nginx 配置如下
php 获取用户ip
$_sever['remote_addr']
<a href="https://imququ.com/post/x-forwarded-for-header-in-http.html">https://imququ.com/post/x-forwarded-for-header-in-http.html</a>
<a href="http://nginx.org/en/docs/http/ngx_http_realip_module.html">http://nginx.org/en/docs/http/ngx_http_realip_module.html</a>
<a href="http://baike.baidu.com/item/iana%e4%bf%9d%e7%95%99%e5%9c%b0%e5%9d%80?fr=aladdin">http://baike.baidu.com/item/iana%e4%bf%9d%e7%95%99%e5%9c%b0%e5%9d%80?fr=aladdin</a>