应使用 Werkzeug 的 ProxyFix 中间件并配置 trusted_proxies 参数,而非直接读取 X-Forwarded-For 头;需同步配置 Nginx 透传头,且手动解析时须校验代理可信性、清理端口、支持 IPv6 归一化。

因为 Nginx(或其他反向代理)挡在了 Flask 前面,request.remote_addr 拿到的只是 Nginx 的连接地址,不是用户真实 IP;而 X-Forwarded-For 头又可能被伪造或未透传,直接读取等于放弃校验。
Flask 里 request.remote_addr 总是 127.0.0.1 或内网地址
这是最典型的症状,说明请求经过了本地反向代理(比如 Nginx 和 Flask 部署在同一台机器),TCP 层看到的远端地址就是代理本机。Flask 默认不解析任何 HTTP 头来覆盖这个值,所以它永远“不知道”原始客户端是谁。
- 不是 bug,是设计:WSGI 规范只保证
remote_addr是 TCP 连接发起方,不负责穿透代理 - 如果你没配 Nginx 透传头,
request.headers.get("X-Forwarded-For")会返回None或空字符串 - 即使 Nginx 配了
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;,Flask 也不会自动信任并替换remote_addr
为什么不能直接用 request.headers.get("X-Forwarded-For", "").split(",")[0].strip()
这条语句在开发环境可能“看起来能用”,但上线后等于把 IP 校验大门敞开——任何客户端都能发一个 X-Forwarded-For: 999.999.999.999 来伪造身份。
-
X-Forwarded-For是纯 HTTP 头,没有任何签名或加密机制 - 如果请求路径中存在多个代理(比如 CDN → Nginx → Flask),
X-Forwarded-For可能含多个 IP,最左才是原始客户端,但前提是每一跳都可信 - 没有校验
request.remote_addr是否属于你配置的可信代理列表,就取这个头,相当于把伪造权交给了公网 - 某些 WSGI 服务器(如 Gunicorn)会把头转成
HTTP_X_FORWARDED_FOR放进environ,直接用request.headers.get()可能拿不到
必须用 ProxyFix 并显式配置 trusted_proxies
Werkzeug 提供的 ProxyFix 是目前最稳妥的解法,它会在 WSGI 入口层就完成可信代理识别 + 头解析 + 地址覆盖,后续所有 request.remote_addr 调用都自然生效。
- 别只写
app.wsgi_app = ProxyFix(app.wsgi_app),缺trusted_proxies参数等于没配 - 可信代理要写具体 IP 或 CIDR,例如
["127.0.0.1", "10.0.1.5", "172.16.0.0/12"];不能写["*"]或留空 - 参数
x_for=1表示只取X-Forwarded-For中最左边 1 个 IP(即原始客户端),num_proxies=1已弃用,优先用trusted_proxies - Nginx 配置必须同步:确保有
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;,且不要用$remote_addr单值——后者在多级代理下会丢失链路信息
手动取 IP 时最容易漏掉的三件事
如果你因框架限制无法用 ProxyFix(比如某些托管平台),必须手写逻辑,以下三点几乎必踩坑:
- 没做
request.remote_addr是否在trusted_proxies中的判断,就直接进X-Forwarded-For解析分支 - 没清理
X-Forwarded-For值里的端口(如"203.0.113.195:12345"),导致ipaddress.ip_network()校验失败 - 没处理 IPv6 压缩格式(如
::1或2001:db8::1),字符串匹配直接出错;ipaddress模块能正确归一化,但得自己调用
真实 IP 获取不是“取哪个字段”的问题,而是“在哪一层、由谁来校验可信链”的问题。绕过 ProxyFix 自己拼逻辑,很容易在边缘 case(比如 IPv6、多级代理、空头、端口混入)上翻车,而且后续维护成本远高于初期多写两行配置。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











