出于多种原因(记录client原始ip、定位、反作弊等), 都需要获取请求发起方的原始ip信息。
这里探讨两种方式:1. PHP纯手动获取,2. Laravel提供的getClientIp()函数
通过$_SERVER()或getenv()方式
方式1:通过x-forwarded-for头部
PHP中通过$_SERVER('HTTP_X_FORWARDED_FOR')获取http request请求头部的x-forwarded-for参数
以下为$_SERVER所有参数实例:
1 | { |
弊端:
- 不经代理时,
$_SERVER['HTTP_X_FORWARDED_FOR']参数未定义 - http request时增加x-forwarded-for参数可伪造用户ip,且该参数可任意输入内容,需进行ip过滤,防注入
该方式下,如存在代理转发,则$_SERVER['HTTP_X_FORWARDED_FOR']中依次存[clientip, proxyip1, proxyip2]
从前往后,第一个合法ip作为用户真实ip
方式2:取$_SERVER['HTTP_CLIENT_IP']或$_SERVER['REMOTE_ADDR']
方式1中,当不经代理转发时,是无法拿到用户ip的;因此下面两种方式可以尝试:
$_SERVER['HTTP_CLIENT_IP']能获取到http request header中的client-ip头部
但此头部非标准规范,并不一定存在,且可随意伪造$_SERVER['REMOTE_ADDR']能获取发起tcp请求的原始ip地址
在不存在代理时,是用户准确ip;存在代理时,该值为最后一个代理ip
这三种方式各有利弊,一般混合使用,但仍有弊端
看一段网上的代码,综合了这三种,处理逻辑:
- 取
HTTP_X_FORWARDED_FOR参数第一个合法ip: 可伪造,兼容存在代理的情形 - 取
HTTP_CLIENT_IP参数:非标准协议不一定有此参数,可伪造 - 取
REMOTE_ADDR参数: 存在代理时获取到的时代理ip,不可伪造
1 | protected function getRealIp() |
上面说的时原生PHP的方式手动取用户ip,下面说说Laravel中支持的getClientIp()函数
通过Laravel的getClientIp()方式
在Laravel(5.2)的Controller或Middleware中,通过$request->getClientIp()可以直接获取用户ip
下面Laravel的获取方式分析:
$request->getClientIp()为调用的vendor/symfony/http-foundation/Request.php中的getClientIp()函数:
可见此函数为直接调用getClientIps()函数获取用户所有ip列表,将第0个返回作为用户真实ip
1 | public function getClientIp() |
vendor/symfony/http-foundation/Request.php的getClientIps()函数
1 | public function getClientIps() |
函数中常量较多,下面将整个逻辑记录如下:
1. $ip = $this->server->get('REMOTE_ADDR');获取REMOTE_ADDR地址
2. 判断REMOTE_ADDR是否在信任代理列表中,信任列表为空或不在信任列表,则返回return array($ip);
注意:这里的逻辑是信任列表为空或REMOTE_ADDR不在信任列表,则返回此REMOTE_ADDR地址。
也就是说默认未配置信任列表的情况下,Laravel取得用户ip其实是REMOTE_ADDR
简言之:使用代理且未设置信任列表时,getClientIp()获取到的是最后一跳连接服务器的ip(也就是代理的ip)
3. 当信任列表非空且REMOTE_ADDR在信任代理列表时,才会进行后续的操作:
3.1 取header中的forwarded参数值
3.2 取header中的x-forwarded-for参数值列表
注意:Laravel的这部分代码略有混淆,常量的定义和header不一致容易导致误解(特别是trustedHeaders信任头部数组)
1 | `HEADER_FORWARDED`常量对应header参数`forwarded` |
3.3 将x-forwarded-for参数值逐一进行如下操作:正则匹配合法ip,检查是否在信任代理列表(在列表则删除),将最终剩余的ip放入列表,倒序列表,返回列表
至此:得到x-forwarded-for中从前到后,且不在信任列表中的ip参数项。$request->getClientIp()取第一项作为用户真实ip
PS: 貌似这里仍然无法避免用户通过修改x-forwarded-for参数来伪造用户ip
总结
- 手动获取处理的方式要考虑各种网络环境
- Laravel的方式依托信任代理列表的思想,将倒数第一个非信任列表代理地址作为用户ip
- 代理模式下,依然无法做到完全的防止用户伪造ip