X-Forwarded-For的一些理解

X-Forwarded-For 是一个 HTTP 扩展头部,主要是为了让 Web 服务器获取访问用户的真实 IP 地址(其实这个真实未必是真实的,后面会说到)。

那为什么 Web 服务器只有通过 X-Forwarded-For 头才能获取真实的 IP?
这里用 PHP 语言来说明,不明白原理的开发者为了获取客户 IP,会使用 $_SERVER[‘REMOTE_ADDR‘] 变量,这个服务器变量表示和 Web 服务器握手的 IP 是什么(这个不能伪造)。
但是很多用户都通过代理来访问服务器的,那么假如使用该全局变量,PHP获取到的 IP 就是代理服务器的 IP(不是用户的)。

可能很多人看的晕乎乎的,那么看看一个请求可能经过的路径:

客户端=>(正向代理=>透明代理=>服务器反向代理=>)Web服务器

其中正向代理、透明代理、服务器反向代理这三个环节并不一定存在。

  • 什么是正向代理呢,很多企业会在自己的出口网关上设置代理(主要是为了加速和节省流量)。
  • 透明代理可能是用户自己设置的代理(比如为了FQ,这样也绕开了公司的正向代理)。
  • 服务器反向代理是部署在 Web 服务器前面的,主要原因是为了负载均衡和安全考虑。

现在假设几种情况:

  • 假如客户端直接连接 Web 服务器(假设 Web 服务器有公网地址),则 $_SERVER[‘REMOTE_ADDR‘] 获取到的是客户端的真实 IP 。
  • 假设 Web 服务器前部署了反向代理(比如 Nginx),则 $_SERVER[‘REMOTE_ADDR‘] 获取到的是反向代理设备的 IP(Nginx)。
  • 假设客户端通过正向代理直接连接 Web 服务器(假设 Web 服务器有公网地址),则 $_SERVER[‘REMOTE_ADDR‘] 获取到的正向代理设备的 IP 。

其实这里的知识点很多,记住一点就行了,$_SERVER[‘REMOTE_ADDR‘] 获取到的 IP 是 Web 服务器 TCP 连接的 IP(这个不能伪造,一般 Web 服务器也不会修改这个头)。

X-Forwarded-For

从上面大家也看出来了,因为有了各种代理,才会导致 REMOTE_ADDR 这个全局变量产生了一定的歧义,为了让 Web 服务器获取到真实的客户端 IP,X-Forwarded-For 出现了,这个协议头也是由 Squid 起草的(Squid 应该是最早的代理软件之一)。

这个协议头的格式:

X-Forwarded-For: client, proxy1, proxy2

client 表示用户的真实 IP,每经过一次代理服务器,代理服务器会在这个头增加用户的 IP(有点拗口)。
注意最后一个代理服务器请求 Web 服务器的时候是不会将自己的 IP 附加到 X-Forwarded-For 头上的,最后一个代理服务器的 IP 地址应该通过$_SERVER[‘REMOTE_ADDR‘]获取。

举个例子:
用户的 IP 为(A),分别经过两个代理服务器(B,C),最后到达 Web 服务器,那么Web 服务器接收到的 X-Forwarded-For 就是 A,B。

那么 PHP 如何获取真实客户端 IP 呢?

$ip = isset($_SERVER[‘HTTP_X_FORWARDED_FOR‘]) ? trim($_SERVER[‘HTTP_X_FORWARDED_FOR‘]) : ‘‘;
if (!$ip) {
    $ip = isset($_SERVER[‘REMOTE_ADDR‘]) ? trim($_SERVER[‘REMOTE_ADDR‘]) : ‘‘;
}
$a = explode(‘|‘, str_replace(‘,‘, ‘|‘, $ip));
$ip = trim($a[0]);

这里预先说明下,假设这两个代理服务器都是好的代理服务器,没有伪造 HTTP_X_FORWARDED_FOR。

配置反向代理

上面一直在说代理,大家可能觉得这到底有啥用?不同类型的代理有不同的目的,对于正向代理来说主要是为了加速并且让局域网的用户有一个真实的 IP 地址,而透明代理则主要是为了一些其他的目的(比如就是不想让别人知道我的 IP),而反向代理主要是企业内部安全和负载均衡考虑,这里主要说下如何配置反向代理。

现在只要是具备一定规模的网站(Web 服务器大于 1 台),为了安全和负载均衡考虑都会在 Web 服务器前面部署反向代理,反向代理有 HAproxy,Nginx,Apache 等等。

这里通过 Nginx 来部署反向代理:

proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

简单的解释下:

  • X-Forwarded-For 表示 Nginx 接收到的头,原样的转发过来(假如不转发,Web 服务器就不能获取这个头)。
  • X-Real-IP,这是一个内部协议头(就是反向代理服务器和 Web 服务器约定的),这个头表示连接反向代理服务器的 IP 地址(这个地址不能伪造),其实个人觉得为了让 PHP 代码保持无二义性,不应该这样设置,可以修改为 proxy_set_header REMOTE_ADDR $remote_addr;

Apache WEB 服务器的 Access 日志如何获取 X-Forwarded-For 头

其实写这篇文章主要是因为自己在 Apache Web 服务器上获取不到 X-Forwarded-For(上层的负载均衡设备确定传递了),搜索了下(在 Apache 官方文档并没有找到解决方案),解决如下:

LogFormat "%{X-Forwarded-For}i %a %h %A %l %u %t \"%r\" %>s %b \"%{Referer}i\"
 \"%{User-Agent}i\"" combined

X-Forwarded-For 安全性

那么很多同学会说,通过 X-Forwarded-For 就能获取到用户的真实 IP,是不是万事大吉了,对于 Web 服务器来说,安全有两个纬度,第一个纬度是 REMOTE_ADDR 这个头,这个头不能伪造。第二个纬度就是 X-Forwarded-For,但是这个头是可以伪造的。

那么谁在伪造呢?,我们分别看下:

正向代理一般是公司加速使用的,假如没有特殊的目的,不应该传递 X-Forwarded-For 头,因为它的上层连接是内部 IP,不应该暴露出去,当然它也可以透明的传递这个头的值(而这个值用户可以伪造)。

透明代理,这个可能是用户自己搭建的(比如FQ),而且在一个用户的请求中,可能有多个透明代理,这时候透明代理就抓瞎了,为了让自己尽量的正确,也会透明的传递这个头的值(而这个值用户可以伪造),当然一些不法企业或者人员,为了一些目的,会改下这个头的值(比如来自世界各地的 IP 地址)。

反向代理,Web 服务器前的反向代理服务器是不会伪造的(同一个公司的),一般会原样传递这个头的值。

那么对应用程序来说,既然这个值不能完全相信,该怎么办呢?这取决于应用的性质:

假如提供的服务可能就是一些非机密服务,也不需要知道用户的真实 IP,那么建议应用程序或者 Web 服务器对 REMOTE_ADDR 做一些限制,比如进行限速等等,也可以放行一些白名单的代理 IP,但是这些白名单 IP 就太难衡量了。

假设你的服务很重要,比如抽奖(一个 IP 只能一次抽奖),这时候你可能想通过 X-Forwarded-For 来获取用户的真实 IP(假如使用 REMOTE_ADDR 则会误杀一片),但是由于 X-Forwarded-For 可能会伪造,所以其实并没有什么好的办法,只能在应用层进行处理了。

时间: 2024-12-29 10:51:48

X-Forwarded-For的一些理解的相关文章

SQL Server :理解数据记录结构

在SQL Server :理解数据页结构我们提到每条记录都有7 bytes的系统行开销,那这个7 bytes行开销到底是一个什么样的结构,我们一起来看下. 数据记录存储我们具体的数据,换句话说,它存在堆表里,或者存在聚集索引的叶子节点.数据记录结构是为了让SQL Server更高效的管理数据.我们来看下数据记录结构示意图: 上图中蓝色部分是所有数据记录部分,绿色部分是表结构里取决于定长/变长列的数据记录部分. 行头系统数据: 用做状态位1的第1字节(8位)是用来定义记录的属性: 第0位:版本信息

Flask中endpoint的理解

在flask框架中,我们经常会遇到endpoint这个东西,最开始也没法理解这个到底是做什么的.最近正好在研究Flask的源码,也就顺带了解了一下这个endpoint 首先,我们看一个例子: @app.route('/user/<name>') def user(name): return 'Hello, %s' % name 这个是我们在用flask框架写网站中最常用的. 通过看源码,我们可以发现: 函数等效于 def user(name) return 'Hello, %s' % name

Python——深入理解urllib、urllib2及requests(requests不建议使用?)

深入理解urllib.urllib2及requests            python Python 是一种面向对象.解释型计算机程序设计语言,由Guido van Rossum于1989年底发明,第一个公开发行版发行于1991年,Python 源代码同样遵循 GPL(GNU General Public License)协议[1] .Python语法简洁而清晰,具有丰富和强大的类库. urllib and urllib2 区别 urllib和urllib2模块都做与请求URL相关的操作,但

关于SVM数学细节逻辑的个人理解(三) :SMO算法理解

第三部分:SMO算法的个人理解 接下来的这部分我觉得是最难理解的?而且计算也是最难得,就是SMO算法. SMO算法就是帮助我们求解: s.t.   这个优化问题的. 虽然这个优化问题只剩下了α这一个变量,但是别忘了α是一个向量,有m个αi等着我们去优化,所以还是很麻烦,所以大神提出了SMO算法来解决这个优化问题. 关于SMO最好的资料还是论文<Sequential Minimal Optimization A Fast Algorithm for Training Support Vector

2.2 logistic回归损失函数(非常重要,深入理解)

上一节当中,为了能够训练logistic回归模型的参数w和b,需要定义一个成本函数 使用logistic回归训练的成本函数 为了让模型通过学习来调整参数,要给出一个含有m和训练样本的训练集 很自然的,希望通过训练集找到参数w和b,来得到自己得输出 对训练集当中的值进行预测,将他写成y^(I)我们希望他会接近于训练集当中的y^(i)的数值 现在来看一下损失函数或者叫做误差函数 他们可以用来衡量算法的运行情况 可以定义损失函数为y^和y的差,或者他们差的平方的一半,结果表明你可能这样做,但是实际当中

理解信息管理系统

1.信息与数据的区别是什么? 数据是记录客观事物,可鉴别的符号,而信息是具有关联性和目的性的结构化,组织化的数据.数据经过处理仍是数据,而信息经过加工可以形成知识.处理数据是为了便于更好的解释,只有经过解释,数据才有意义,才可以成为信息.可以说信息是经过加工以后,对客观世界产生影响的数据. 2.信息与知识的区别是什么? 信息是具有关联性和目的性的结构化,组织化的数据,知识是对信息的进一步加工和应用,是对事物内在规律和原理的认识.信息经过加工可以形成知识. 3.举一个同一主题不同级别的数据.信息.

深度理解div+css布局嵌套盒子

1. 网页布局概述 网页布局的概念是把即将出现在网页中的所有元素进行定位,而CSS网页排版技术有别于传统的网页排版方法,它将页面首先在整体上使用<div>标记进行分块,然后对每个快进行CSS定位以及设置显示效果,最后在每个块中添加相应的内容.利用CSS排版方法更容易地控制页面每个元素的效果,更新也更容易,甚至页面的拓扑结构也可以通过修改相应的CSS属性来重新定位.  2. 盒子模型 盒子模型是CSS控制页面元素的一个重要概念,只有掌握了盒子模型,才能让CSS很好地控制页面上每一个元素,达到我们

深入理解Java:类加载机制及反射

一.Java类加载机制 1.概述 Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数,属性和方法等,Java允许用户借由这个Class相关的元信息对象间接调用Class对象的功能. 虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 2.工作机制 类装载器就是寻找类的字节码文件,并构造出类在JVM内部表示

八幅漫画理解使用 JSON Web Token 设计单点登录系统

原文出处: John Wu 上次在<JSON Web Token – 在Web应用间安全地传递信息>中我提到了JSON Web Token可以用来设计单点登录系统.我尝试用八幅漫画先让大家理解如何设计正常的用户认证系统,然后再延伸到单点登录系统. 如果还没有阅读<JSON Web Token – 在Web应用间安全地传递信息>,我强烈建议你花十分钟阅读它,理解JWT的生成过程和原理. 用户认证八步走 所谓用户认证(Authentication),就是让用户登录,并且在接下来的一段时

谈谈你对Hibernate的理解

答: 1. 面向对象设计的软件内部运行过程可以理解成就是在不断创建各种新对象.建立对象之间的关系,调用对象的方法来改变各个对象的状态和对象消亡的过程,不管程序运行的过程和操作怎么样,本质上都是要得到一个结果,程序上一个时刻和下一个时刻的运行结果的差异就表现在内存中的对象状态发生了变化. 2.为了在关机和内存空间不够的状况下,保持程序的运行状态,需要将内存中的对象状态保存到持久化设备和从持久化设备中恢复出对象的状态,通常都是保存到关系数据库来保存大量对象信息.从Java程序的运行功能上来讲,保存对