NGINX杂谈——flask_limiter的IP获取(怎么拿到真实的客户端IP)
本篇博客将 flask_limiter 作为切入点,来记录一下自己对 remote_addr 和 proxy_add_x_forwarded_for 两个变量、X-Real-IP 和 X-Forwarded-For 两个字段的一些理解。
flask_limiter 的文档。
如果开发过 Flask + NGINX 的项目,又使用了 flask_limiter 做 IP 限制,就有可能会遇上所有用户共享限制的问题(一个用户用超次数,另一个用户也无法使用)。这是因为使用了 flask_limiter 提供的默认 key_func——get_remote_address 经过 NGINX 代理拿不到真实的用户 IP。接下来,我们通过他的源码来分析具体的原因,下文可能很长,且没有直接了当的解决方法,如果只为解决问题,建议搜索其他文章。
flask_limiter 的 IP 获取
以下是 flask_limiter 获取 IP 的代码
def get_remote_address():
"""
:return: the ip address for the current request (or 127.0.0.1 if none found)
"""
return request.remote_addr or '127.0.0.1'
可以看到 flask_limiter 是通过 request.remote_addr 获取的IP。这里的 remote_addr 变量和 NGINX 的 $remote_addr 是一样的,都是直接从 TCP 连接信息中获取的,基本上不能被伪造。即使伪造了,TCP 连接都不知道你是谁,无法进行三次握手,那就根本建立不了连接。
所以 remote_addr 可以理解为就是和当前服务正在通信的客户端的真实 IP 地址。
而如果加入 NGINX 代理,再进行 http 访问的时候,用户就不再和 Python 服务直接建立链接。正在通信的客户端就不再是真实的客户端,而是代理客户端。
我们通过netstat -n | grep -E '\.2081|\.80'来查看 TCP 连接情况也可以证实这一点。根据输出可以发现用户(192.168.17.167)和 NGNIX 服务端(192.168.19.165:80)建立了 TCP 链接,NGINX 客户端(127.0.0.1:64671)和 Python 服务端(127.0.0.1:9001)建立了 TCP 链接。所以经过 NGINX 代理,这个时候 Python 服务端拿到的 remote_addr 就是 NGINX 客户端的 IP。
Proto Recv-Q Send-Q Local Address Foreign Address (state)
tcp4 0 0 192.168.19.165.80 192.168.17.167.47362 ESTABLISHED
tcp4 0 0 192.168.19.165.80 192.168.17.167.47360 ESTABLISHED
tcp4 0 0 192.168.19.165.80 192.168.17.167.47130 TIME_WAIT
tcp4 0 0 127.0.0.1.9001 127.0.0.1.64671 TIME_WAIT
既然我们无法通过 request.remote_addr 来获取真实的用户 IP,那我们就只能在 NGINX 代理的时候设置请求头,然后在 Python 服务端通过请求头来获取用户的真实 IP 了。flask_limiter 的问题就可以通过自定义 key_func 去获取我们在 NGINX 设置的请求头来解决。
NGINX 常见的和 IP 有关的设置有两个:
location /flask/ {
proxy_pass http://localhost:9001/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
以上是两种常见的设置方法,但是我们还是要理清楚他们的原理。这样我们进行多层 NGINX 代理,或者用别的代理时才能避免错误。
X-Forwarded-For
HTTP/1.1(RFC 2616)协议并没有对 X-Forwarded-For 进行定义,所以 X-Forwarded-For 一直以来都不是标准的HTTP头信息,IANA 的注册信息也可以佐证这一点。我看网上其他的一些博客说它后来被写入 Forwarded HTTP Extension(RFC 7239)标准,甚至连百度百科也说,IETF 在 Forwarded-For HTTP 头字段标准化草案中正式提出。我印象中 RFC 7239 就是因为 X-Forwarded-For 不标准才提出 Forwarded 这个新的请求头字段。为此,我去翻看了 RFC 7239 的历史版本,确认了没有哪一版 RFC 7239 为 X-Forwarded-For 正名过。如果看过 RFC 6648 或 RFC 7231 的 "8.3.1. Considerations for New Header Fields"小节,就会知道"X-"开头的头信息字段其实是不被认可的。
作为一个不标准的请求头字段,X-Forwarded-For 却被各大 http 代理、负载均衡等转发服务追捧,尽管 RFC 7239 提案已经进入 proposed standard 状态,也无法改变 X-Forwarded-For 的地位。
X-Forwarded-For的工作原理很简单,只要每一个代理服务都在定义 X-Forwarded-For 时都追加上上一个代理或客户的 IP(这是真实的,无法被仿造的),就可以记录下 http 请求链中的所有IP地址,以便后续的每个服务访问。
有一些爬虫教程会介绍通过修改 X-Forwarded-For 绕过 IP 限制的方法,但这种方法其实是在利用网页服务开发者的不严谨,并不是真的欺骗了网页服务。正如上文所说,一个合格的代理,是会追加上上一个代理或客户的IP的。比如 NGINX 的 $proxy_add_x_forwarded_for,就是会包含真实的 IP 的,他的值为客户端请求头的 X-Forwarded-For字段的值 + 客户端的真实 IP。
Eg. 如果客户端(0.0.0.0)请求头的 X-Forwarded-For 字段为
127.0.0.1,则 proxy_add_x_forwarded_for 为127.0.0.1, 0.0.0.0;如果客户端(0.0.0.0)请求头的 X-Forwarded-For字段为127.0.0.1, 196.128.0.1,则 proxy_add_x_forwarded_for 为127.0.0.1, 196.128.0.1, 0.0.0.0。所以设置得当的话,X-Forwarded-For 和 X-Real-IP 都是可以拿到真实的 IP 的。
有一些防伪造 IP 的教程会说要把 X-Forwarded-For 和 X-Real-IP 一样设置为 $remote_addr,这其实也是不合理的。X-Forwarded-For 和 X-Real-IP 诞生的目的是不一样的,X-Real-IP 是为了记录最外一层代理面向的 IP,保障对服务和代理(这里说的代理指的是由网页服务提供方提供的代理,可以理解为反向代理)这个整体来说,请求来自合法的 IP;X-Forwarded-For 则是为了记录完整的 IP 链,保障对客户来说,不管他使用什么样的代理(这里说的代理指的是由网页服务提供方提供的代理加上用户自己使用的代理,比如 VPN),只要代理可靠且不以隐匿为目的,网页服务总能契合他的需求。
Eg. 就像天气一样,我们不能因为用户使用了北京的代理,就给他推北京的天气,我们要追踪到最原始的 IP 地址,并且给他推送该IP对应的地区的天气。
X-Real-IP
如果说 X-Forwarded-For 是一个野孩子,那 X-Real-IP 则更是浮萍漂泊。好歹 NGINX 代理给 X-Forwarded-For 专门设了一个传递变量,X-Real-IP 却只能是 remote_addr 的载体,甚至随便换个名字也没有影响,我们只需要起一个 NGINX 支持的,又没有在标准里的名字就可以,叫阿猫阿狗也行。
location /flask/ {
proxy_pass http://localhost:9001/;
proxy_set_header A-MAO-A-GOU $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
这样设置最后通过 A-MAO-A-GOU 也可以拿到真实的 IP。当然,采取和大家一样的标准是很重要的。
上文介绍了 X-Forwarded-For 会存整个 IP 链,那我们通过 IP 链就可以反推会最外层代理(这里说的代理指的是由网页服务提供方提供的代理,可以理解为反向代理)面对的客户端了。
比如,X-Forwarded-For 字段的值是0.0.0.0, 1.1.1.1, 2.2.2.2, 3.3.3.3, 4.4.4.4,我们知道3.3.3.3和4.4.4.4是我们的代理服务器,那最外层代理面对的客户端就是2.2.2.2。
不过一般来讲,这样操作需要开发人员去了解代理的情况,这对开发人员也是一个负担,如果代理不只是一条链路就更麻烦。所以更好、更直接的方法就是商量好一个字段,让大家都使用它来传递信息,比如 X-Real-IP。这样代理服务通过X-Real-IP给出一个IP,开发人员利用这个IP进行简单的校验;而代理服务通过 X-Forwarded-For 给出的IP链,开发人员往往取最前的一条,当成客户的真实IP。
以上就是关于 X-Real-IP 和 X-Forwarded-For 的记录,希望有生之年可以看到 RFC 7239 这个草案付诸实践吧。
NGINX杂谈——flask_limiter的IP获取(怎么拿到真实的客户端IP)的更多相关文章
- Nginx+Docker部署模式下 asp.net core 获取真实的客户端ip
目录 Nginx+Docker部署模式下 asp.net core 获取真实的客户端ip 场景 过程还原 结论 参考资料 Nginx+Docker部署模式下 asp.net core 获取真实的客户端 ...
- Django 如何获取真实远程客户端IP
问题简述 我们知道HttpRequest.META字典包含所有HTTP头部信息(可用的头部信息取决于客户端和服务器).一般情况下,HttpRequest.META.get('REMOTE_ADDR') ...
- 获取SQL Server中连接的客户端IP地址[转]
有时候需要获取连接到SQL Server服务器上的客户端IP地址,用什么办法呢? SELECT *FROM sys.dm_exec_connections WHERE session_id = @@S ...
- 配置nginx以获取真实的客户端ip地址
当我们使用了nginx来转发客户端的请求以后,tomcat是无法正确获取到客户端的ip地址的,而是获取到配置了nginx的那台服务器的ip地址.因为tomcat所接收到的请求是通过nginx发出来的( ...
- NGINX前端代理TOMCAT取真实客户端IP
nginx前端代理tomcat取真实客户端IP 使用Nginx作为反向代理时,Tomcat的日志记录的客户端IP就不在是真实的客户端IP,而是Nginx代理的IP.要解决这个问题可以在Nginx配置一 ...
- nginx+tomcat集群配置(3)---获取真实客户端IP
前言: 在初步构建的nginx+tomcat服务集群时, 发现webserver获取到的客户端ip都是同一个, 皆为作为反向代理服务的nginx所在的机器IP. 这不太符合我们的基本需求, 为将来的数 ...
- nginx反向代理node.js获取客户端IP
使用Nginx做node.js程序的反向代理,会有这么一个问题:在程序中获取的客户端IP永远是127.0.0.1 如果想要拿到真实的客户端IP改怎么办呢? 一.首先配置Nginx的反向代理 proxy ...
- nginx 获取源IP 获取经过N层Nginx转发的访问来源真实IP
1. nginx 配置文件中获取源IP的配置项 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; #一般的we ...
- nginx中获取真实的客户端访问IP
date : 2019-06-28 16:54:50 author: headsen chen notice: 个人原创 1,必需要先搞清楚的基本概念 1.1 什么是remote_addr ...
随机推荐
- 10分钟学会VS NuGet包私有化部署
前言 我们之前实现了打包发布NuGet,但是发布后的引用是公有的,谁都可以访问,显然这种方式是不可取的. 命令版本:10分钟学会Visual Studio将自己创建的类库打包到NuGet进行引用(ne ...
- djang2.1教育平台02
在次申明,之所以重做这个资料是因为原幕课教程漏洞太多,新手根本没有办法正常照些学习,我凭着老男孩python 课程基础,重做这个教程 ,更改版本为当前最新版本,为了方法以后的人学习,并不是照着原文照 ...
- Java入门准备:Java开发环境的安装与卸载
Java的三大版本 JavaSE:标准版 JavaME:嵌入式开发 JavaEE:企业级开发 JDK(Java Development Kit):Java开发者工具包 JRE(Java Runtime ...
- KMP算法解决字符串匹配问题
要解决的问题 假设字符串str长度为N,字符串match长度为M,M <= N, 想确定str中是否有某个子串是等于match的.返回和match匹配的字符串的首字母在str的位置,如果不匹配, ...
- PHP匿名类的用法
在PHP7之后,PHP中加入了匿名类的特性.匿名类和匿名方法让PHP成为了更现代化的语言,也让我们的代码开发工作越来越方便.我们先来看看匿名类的简单使用. // 直接定义 $objA = new cl ...
- ecshop首页调用团购说明
要在首页调用购买. 发现在首页还不能直接调用团购说明.查看了一下代码发现要修改下才能调 打开根目录的 index.php 文件找到 $sql = 'SELECT gb.act_id AS group_ ...
- 基于pgpool搭建postgressql集群部署
postgresql集群搭建 基于pgpool中间件实现postgresql一主多从集群部署,这里用两台服务器作一主一从示例 虚拟机名 IP 主从划分 THApps 192.168.1.31 主节点 ...
- Elasticsearch -head 查询报 406错误码
问题:利用Elasticsearch -head插件不能查看数据或者在Elasticsearch -linux的curl命令操作时总是提示: {"error":"Cont ...
- Charles的breakpoint功能
修改请求报文 比如,前端已经控制了输入内容,而我们需要验证接口是否做了校验,这时候怎么测试? 可以通过charles抓包,修改请求报文,修改为在页面上无法输入的内容,发出去然后看后台怎么处理. 修改返 ...
- python日志loguru
文档:https://loguru.readthedocs.io/en/stable/overview.html#installation pip install loguru 使用 基本使用 ##终 ...