前言
因为现在很流行前后端分离,这样可以更好地管理前后端代码以及前后端分工。不过前后端分离也不是万能的,前后端分离也带来了一些问题,其中跨域访问问题就是很常见的一个。通过设置相应头可以解决跨域问题,可是同时每次请求后端接口的时候,前端都需要多发送一个 OPTIONS
请求咨询自己是否可以请求,请求太多看起来十分影响颜值。于是我们选择了使用反向代理后端的 API
使前后端都在同一个域下进而规避掉跨域的问题。
一开始使用自然是相当美好的,这个跟恋爱刚开始一样。然而上周有同事做一个需要收集客户IP的需求时才发现了后端获取到的客户IP并不正确!
检查PHP代码
同事是通过 Laravel
框架的app(\Illuminate\Http\Request::class)->ip()
来获取客户端的IP的,找到它:
找到方法定义发现是调用了getClientIp()
这个方法来获取客户端IP的,然而Ctrl+F
大法并没有在当前类找到该方法,于是向上翻找,发现这个类是继承了Symfony\Component\HttpFoundation\Request
类,OK,继续往上找:
终于找到了getClientIp()
这个方法,结果发现居然是通过getClientIps()
这个方法获得的,好吧,成功就在眼前,让我们继续找到getClientIps()
:
出现了!原来是通过$_SERVER['REMOTE_ADDR']
这个变量来获取客户端IP的!然后判断是不是来自可信任的代理,因为静态变量$trustedProxies
默认情况下没有设置,所以isFromTrustedProxy()
方法返回的值是false
,所以直接返回了$_SERVER['REMOTE_ADDR']
获取到的值。
接下来我们尝试打印Illuminate\Http\Request::ip()
方法获取到的IP以及$_SERVER['REMOTE_ADDR']
。代码如下:
1 | dump('app(Illuminate\Http\Request::class)->ip(): ' . app(Illuminate\Http\Request::class)->ip()); |
输出结果如下:
果不其然,如我们所料的一样!经查询发现获得的IP就是我们项目所在的服务器!因为代理导致REMOTE_ADDR
没有能正确获取到客户端那边的IP。
解决方案(Apache)
原因找到之后,相应的解决方案也就呼之欲出。因为我们用的是Apache
,所以首先给出Apache
的解决方案。
Apache
需要启用一个remoteip
的模块,然后在api
的虚拟主机配置文件中加上一行代码即可,如下:
1 | <VirtualHost _default_:443> |
加好之后,我们reload apache
看看效果:
Awesome!果然显示的是真实的客户IP了!
解决方案(Nginx)
后续有其他同事反馈说另一个项目也出现了同样的问题,我一看,阿咧,Nginx
,好吧,难不倒我的。经过一番搜索,终于找到了解决方案。
首先我们的前端虚拟主机配置文件如下:
1 | location /api { |
然后在API
虚拟主机配置文件中加入下面一行:
1 | server { |
因为我在服务器上更改了API域名的hosts,把域名解析到了本地,所以这里set_real_ip_from
就填入127.0.0.1
,如果你是关联其他局域网内的服务器并在hosts设置了域名的IP,那么填入对应的内网IP即可。如果你需要反向代理的API不用hosts,那么set_real_ip_from
这一行可以忽略。
然后reload Nginx
即可。
解决方案(Laravel)
其实直接通过设置上文所说的$trustedProxies
静态变量即可正确获取客户端的真实IP。由于时间关系,我仅测试了Laravel 5.8
版本,使用下面代码即可正确获取客户端真实IP:
1 | request()->setTrustedProxies(request()->getClientIps, \Illuminate\Http\Request::HEADER_X_FORWARDED_FOR); |
为了方便,可以新建一个中间件然后将上面设置trustedProxies
静态变量的那行代码加进去,然后就可以完美解决无法获取到客户端真实IP的问题啦!
Happy Ending
本篇文章到这就全部结束了,感谢大家的阅读。希望大家能从这篇文章中得到帮助。如果你对这篇文章有任何意见或建议,都欢迎给我发送邮件,优质的文章离不开读者的反馈。