X-Forwarded-For 的一些理解
X-Forwarded-For 是一个 HTTP 扩展头部,
主要是为了让 Web 服务器获取访问用户的真实 IP 地址(其实这个真实未必是真实的,后面会说到)。
那为什么 Web 服务器只有通过 X-Forwarded-For 头才能获取真实的 IP?
=================
X-Forwarded-For: client1, proxy1, proxy2
第一个参数是clientip,即实际访问设备的ip,其他的是代理服务器ip,应该是微信在全国各地的nginx代理服务器。
解决方案:
if(!StringUtils.isEmpty(ip)) {
ip = ip.split(",")[0];
}
=======
这里用 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 可能会伪造,所以其实并没有什么好的办法,只能在应用层进行处理了。
https://www.jianshu.com/p/15f3498a7fad
X-Forwarded-For 的一些理解的更多相关文章
- SQL Server 存储(2/8):理解数据记录结构
在SQL Server :理解数据页结构我们提到每条记录都有7 bytes的系统行开销,那这个7 bytes行开销到底是一个什么样的结构,我们一起来看下. 数据记录存储我们具体的数据,换句话说,它存在 ...
- Flask中endpoint的理解
在flask框架中,我们经常会遇到endpoint这个东西,最开始也没法理解这个到底是做什么的.最近正好在研究Flask的源码,也就顺带了解了一下这个endpoint 首先,我们看一个例子: @app ...
- SQL Server :理解数据记录结构
原文:SQL Server :理解数据记录结构 在SQL Server :理解数据页结构我们提到每条记录都有7 bytes的系统行开销,那这个7 bytes行开销到底是一个什么样的结构,我们一起来看下 ...
- 理解OpenShift(1):网络之 Router 和 Route
理解OpenShift(1):网络之 Router 和 Route 理解OpenShift(2):网络之 DNS(域名服务) 理解OpenShift(3):网络之 SDN 理解OpenShift(4) ...
- 理解OpenShift(2):网络之 DNS(域名服务)
理解OpenShift(1):网络之 Router 和 Route 理解OpenShift(2):网络之 DNS(域名服务) 理解OpenShift(3):网络之 SDN 理解OpenShift(4) ...
- 理解ASP.NET Core - [04] Host
注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 本文会涉及部分 Host 相关的源码,并会附上 github 源码地址,不过为了降低篇幅,我会 ...
- 理解CSS视觉格式化
前面的话 CSS视觉格式化这个词可能比较陌生,但说起盒模型可能就恍然大悟了.实际上,盒模型只是CSS视觉格式化的一部分.视觉格式化分为块级和行内两种处理方式.理解视觉格式化,可以确定得到的效果是应 ...
- 彻底理解AC多模式匹配算法
(本文尤其适合遍览网上的讲解而仍百思不得姐的同学) 一.原理 AC自动机首先将模式组记录为Trie字典树的形式,以节点表示不同状态,边上标以字母表中的字符,表示状态的转移.根节点状态记为0状态,表示起 ...
- 理解加密算法(三)——创建CA机构,签发证书并开始TLS通信
接理解加密算法(一)--加密算法分类.理解加密算法(二)--TLS/SSL 1 不安全的TCP通信 普通的TCP通信数据是明文传输的,所以存在数据泄露和被篡改的风险,我们可以写一段测试代码试验一下. ...
- node.js学习(三)简单的node程序&&模块简单使用&&commonJS规范&&深入理解模块原理
一.一个简单的node程序 1.新建一个txt文件 2.修改后缀 修改之后会弹出这个,点击"是" 3.运行test.js 源文件 使用node.js运行之后的. 如果该路径下没有该 ...
随机推荐
- My thoughts after NOIP 2018(2)
又一次陷入迷茫了呢. - 大概是因为是因为自招政策要改变了吧? - 大概是因为前路在一点一点变得难走吧? - 大概是因为OI和学习实在太难平衡了吧? 未来的一切都已经不再在我控制的范围之内,不迷茫才怪 ...
- MYCAT分库分表
一.整体架构 1.192.168.189.130:mysql master服务,两个数据库db_store.db_user,db_store做了主从复制 db_user: 用户表users为分片表 数 ...
- 函数和常用模块【day05】:装饰器前戏(二)
本节内容 嵌套函数 局部作用域和全局作用域的访问顺序 一.嵌套函数 1.定义 在一个函数的函数体内,用def 去声明一个函数,而不是去调用其他函数,称为嵌套函数. 1 2 3 4 5 6 7 8 9 ...
- python---ORM之SQLAlchemy(1)
定义一个类,ORM(对象关系映射)将这个类转换为sql语句,使用pymysql进行执行 一,底层处理 使用engine/connectionpooling/dialect进行数据库操作,engine使 ...
- ruby where用法
用法1 Subject.where(").order("name") 用法2 与find方法不同的是,where方法返回的结果不是数组而是ActiveRelation,这 ...
- golang基础数据结构链表
链表 链表(Linked list),是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer). 每个节点包含下一个节点的地址,这样把所有的节点串起来了, ...
- Java中 break continue return 的区别
1.break break :跳出当前循环:但是如果是嵌套循环,则只能跳出当前的这一层循环,只有逐层break才能跳出所有循环: ; i < ; i++) { ) { break; // 在执行 ...
- 使用paramiko远程执行命令、下发文件
写部署脚本时,难免涉及到一些远程执行命令或者传输文件. 之前一直使用sh库,调用sh.ssh远程执行一些命令,sh.scp传输文件,但是实际使用中还是比较麻烦的,光是模拟用户登陆这一点,还需要单独定义 ...
- 基于Redisson实现分布式锁
前言 最近开发了几个微服务上线了,发现定时任务执行了很多次,查看rancher发现这几个微服务都是多实例的,也就是说定时任务执行了多次,恰好所用框架中使用的是Redisson, 正好记录下使用Redi ...
- OptionParser命令参数介绍及使用
使用optionParse解析命令行参数分以下几个步骤: 创建parser实例 使用add_option添加我们要处理的命令行参数 得到解析sys.argv后的options对象,查看用户的输入 代码 ...