Rootop 服务器运维与web架构

nginx反向代理,后端服务器获取真实ip原理

环境:nginx做反向代理,apache做后端服务器

nginx部分配置代码:
upstream apache{
server 127.0.0.1:8080; # 后端真实服务器地址及端口
}

server {
listen 80;

server_name www.a.com;
root /usr/share/nginx/html;

location / {
proxy_pass http://apache;
proxy_set_header ClientIpGetFromNginx $remote_addr;
}

首先先去看一下nginx内置的变量: http://blog.csdn.net/iinel/article/details/4321383
变量 $remote_addr 代表客户端ip地址

通常来说nginx反向代理会添加一个请求头
proxy_set_header X-Forwarded-For $remote_addr;
以此传递客户端ip到后端服务器。

为了方便理解,我这里改一下头的名称:
proxy_set_header ClientIpGetFromNginx $remote_addr;

此时用浏览器访问一下,去查看后端服务器的访问日志如下
127.0.0.1 – – [01/Sep/2017:10:31:10 +0800] 后面内容省略···
可以看到客户端ip为127.0.0.1也就是nginx的ip,(我nginx和后端服务器在一起)

这样一来获取的ip是错误的,那怎样获取正确ip呢?

先来看一下apache日志格式。

<IfModule log_config_module>
LogFormat “%h %l %u %t \”%r\” %>s %b \”%{Referer}i\” \”%{User-Agent}i\”” combined
LogFormat “%h %l %u %t \”%r\” %>s %b” common

<IfModule logio_module>
LogFormat “%h %l %u %t \”%r\” %>s %b \”%{Referer}i\” \”%{User-Agent}i\” %I %O” combinedio
</IfModule>
CustomLog “logs/access_log” combined
</IfModule>

combined、common、combinedio是apache的3中日志格式,默认用 combined 方式

就需要修改
LogFormat “%h %l %u %t \”%r\” %>s %b \”%{Referer}i\” \”%{User-Agent}i\”” combined

LogFormat “%{ClientIpGetFromNginx}i %l %u %t \”%r\” %>s %b \”%{Referer}i\” \”%{User-Agent}i\”” combined

这样一来,就等于从nginx的请求头中取 ClientIpGetFromNginx 变量做为日志开头的ip,即客户端ip
上面i的意思就是从请求头中取ClientIpGetFromNginx,i就代表请求头
可以参考apache日志格式: http://blog.sina.com.cn/s/blog_672c5a470100xj7z.html

保存配置,再访问一次,再查看apache的访问日志:
192.168.10.105 – – [01/Sep/2017:11:50:41 +0800] 后面内容省略···

可以看到ip是 192.168.10.105 ,这才是客户端的真实ip。

总结:
nginx获取客户端ip是用$remote_addr变量,这个ip是真实的。
后端服务器如果用$remote_addr获取,那么这个ip其实是nginx的ip。
如果nginx设置了传递变量X-Forwarded-For $remote_addr,那么后端用X-Forwarded-For取真实ip

在没有反向代理或CDN的情况下,是不能用X-Forwarded-For获取客户端ip的。因为浏览器是不会发送这个字段的,如果用程序模拟一个访问,这个值是可以被伪造的。

可能会有个想法,如果设置ip传递为
proxy_set_header remote_addr $remote_addr;

是不是就可以不用修改日志格式或者修改代码了?
结果是不行的,会出现一条这样的日志:
127.0.0.1 – – [01/Sep/2017:13:53:42 +0800] 后面内容省略···
ip是127.0.0.1,是nginx的ip地址。表明$remote_addr是不能伪造的。

在上面的设置,通过php中一个数组 $_SERVER 可以获取到:
$_SERVER 是一个包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组。

通过以下php代码可以看到:
<?php

foreach ($_SERVER as $k => $v)
{
echo $k . “============” . $v . “</br>”;
}

保存为1.php,然后去访问这个页面(当然必须有php运行环境)。结果如下

在阿里云官方网站文档中 https://help.aliyun.com/knowledge_detail/40535.html 也可以看到关于ecs获取客户端ip的方法。是一样的。

知识点:
为啥叫 X-Forwarded-For 而不是别的呢?
这是因为当时的squid之类的缓存软件用的比较广,软件官方文档里就用这个名作为标准了。时间一久,大家都遵守这个习惯了。
在标准请求头中是没有这个变量名的。

原创文章,转载请注明。本文链接地址: https://www.rootop.org/pages/3864.html

作者:Venus

服务器运维与性能优化

评论已关闭。