获取客户端真 实IP
为什么需要获取真实IP
在现代 Web 架构中,为了应对大量用户访问,网站通常会部署在以下架构之后:
- WAF(如 Cloudflare WAF、AWS WAF)
- 负载均衡器(如 AWS ALB/ELB、阿里云 SLB)
- CDN 服务(如 Cloudflare、AWS CloudFront)
- 反向代理(如 Nginx、Apache)
因此,一个典型的客户端请求流程如下:
如果没有特殊配置,Nginx 的访问日志只能记录到直接连接的上游设备 IP(负载均衡器或 CDN 的 IP),而不是真实用户的 IP 地址。这会导致:
- 日志分析困难:无法识别真实用户行为
- 安全防护失效:无法基于用户 IP 进行访问控制
- 地域限制失效:无法根据用户地理位置提供服务
- 监控告警不准确:无法准确识别攻击来源
技术原理
HTTP 头部传递机制
负载均衡器和 CDN 通常会在 HTTP 请求头中添加真实用户的 IP 信息:
头部字段 | 使用场景 | 示例 |
---|---|---|
X-Forwarded-For | 标准字段,大多数代理使用 | X-Forwarded-For: 1.2.3.4, 172.16.1.10 |
X-Real-IP | 简单代理环境 | X-Real-IP: 1.2.3.4 |
CF-Connecting-IP | Cloudflare CDN 专用 | CF-Connecting-IP: 1.2.3.4 |
Forwarded | RFC 7239 标准 | Forwarded: for=1.2.3.4;proto=https |
关于上述字段的详细介绍,可以参考:
连接来源 IP vs 真实客户端 IP
连接来源 IP
- 定义:与 Nginx 建立 TCP 连接的设备 IP
- 获取方式:通过系统调用
getpeername()
获得 - 特点:无法被应用层伪造,是真实的网络层信息
- 在 Nginx 中:对应
$realip_remote_addr
变量
真实客户端 IP
- 定义:最终用户的实际 IP 地址
- 获取方式:从 HTTP 头部解析
- 特点:可能被中间代理修改或伪造
- 在 Nginx 中:经过处理后的
$remote_addr
变量
核心配置指令
可以参考Nginx Real IP Module Documentation
real_ip_header
作用:指定从哪个 HTTP 头部获取真实 IP
# 常用配置
real_ip_header X-Forwarded-For; # 标准配置
real_ip_header X-Real-IP; # 简单代理
real_ip_header CF-Connecting-IP; # Cloudflare CDN
set_real_ip_from
作用:定义可信任的代理 IP 范围(白名单机制)
# 基本语法
set_real_ip_from ip_address;
set_real_ip_from ip_address/netmask;
set_real_ip_from unix:;
# 实际示例
set_real_ip_from 10.0.0.0/8; # 内网段
set_real_ip_from 172.16.0.0/12; # 私有网络
set_real_ip_from 192.168.1.100; # 特定 IP
real_ip_recursive
作用:启用递归处理多层代理
real_ip_recursive on; # 启用(推荐)
real_ip_recursive off; # 禁用(默认)
工作机制详解
白名单验证机制
set_real_ip_from
采用白名单机制:
set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;
工作流程
- 检查连接来源 IP
- 如果来源 IP 在白名单中 → 信任请求 → 解析 HTTP 头部
- 如果来源 IP 不在白名单中 → 不信任请求 → 忽略 HTTP 头部
示例对比
来源 IP 在白名单中:
连接来源: 10.0.1.100 (✓ 在 10.0.0.0/8 范围内)
X-Forwarded-For: 1.2.3.4
结果: $remote_addr = 1.2.3.4
来源 IP 不在白名单中:
连接来源: 8.8.8.8 (✗ 不在白名单范围内)
X-Forwarded-For: 1.2.3.4
结果: $remote_addr = 8.8.8.8 (忽略头部信息)
多值 X-Forwarded-For 处理算法
X-Forwarded-For 格式
X-Forwarded-For: client_ip, proxy1_ip, proxy2_ip, proxy3_ip
←────────── 从左到右:客户端到服务器 ──────────→
非递归模式 real_ip_recursive off
选择 最后一个(最右边) IP:
set_real_ip_from 192.168.1.0/24;
real_ip_header X-Forwarded-For;
real_ip_recursive off;
# X-Forwarded-For: 1.2.3.4, 172.16.1.10, 192.168.1.50
# 选择: 192.168.1.50
递归模式 real_ip_recursive on
从右到左检查,跳过可信 IP,选择第一个不可信 IP:
set_real_ip_from 192.168.1.0/24; # 可信代理1
set_real_ip_from 172.16.1.0/24; # 可信代理2
real_ip_header X-Forwarded-For;
real_ip_recursive on;
# X-Forwarded-For: 1.2.3.4, 172.16.1.10, 192.168.1.50
# 处理过程:
# 1. 检查 192.168.1.50 → 可信,跳过
# 2. 检查 172.16.1.10 → 可信,跳过
# 3. 检查 1.2.3.4 → 不可信,选择
# 结果: $remote_addr = 1.2.3.4
总结一下:当我们把 real_ip_header
设置为 X-Forward-For
时,如果没有启用 real_ip_recursive
,取到的 $remote_addr
是 X-Forward-For
中最右边的 ip
,无论是否设置了 set_real_ip_from
;
如果启用了 real_ip_recursive on
,那么就进行递归匹配,跳过可信 IP,选择第一个不可信 IP,在上面例子中就是最左边的 ip
,也就是排除设置了 set_real_ip_from
(可信地址外的)
不同匹配程度的处理逻辑
假设 X-Forwarded-For: 1.2.3.4, 203.0.113.5, 172.16.1.10
,连接来源为 172.16.1.10
:
情况1:只匹配最后一个
set_real_ip_from 172.16.0.0/12; # 只信任 172.16.1.10
# 处理: 172.16.1.10(跳过) → 203.0.113.5(选择)
# 结果: $remote_addr = 203.0.113.5
情况2:匹配最后两 个
set_real_ip_from 172.16.0.0/12; # 信任 172.16.1.10
set_real_ip_from 203.0.113.0/24; # 信任 203.0.113.5
# 处理: 172.16.1.10(跳过) → 203.0.113.5(跳过) → 1.2.3.4(选择)
# 结果: $remote_addr = 1.2.3.4
情况3:全部匹配
set_real_ip_from 172.16.0.0/12; # 信任 172.16.1.10
set_real_ip_from 203.0.113.0/24; # 信任 203.0.113.5
set_real_ip_from 1.2.3.0/24; # 信任 1.2.3.4
# 处理: 所有IP都可信 → 选择最左边的IP
# 结果: $remote_addr = 1.2.3.4
除了上述情况,还有其他 情况,比如:
情况4:连接来源不可信,也就是没有任何一个IP在可信列表中
set_real_ip_from 172.16.0.0/12; # 信任 172.16.1.10
# 但连接来源是: 8.8.8.8 (不在可信列表中)
# X-Forwarded-For: 1.2.3.4, 203.0.113.5, 172.16.1.10
# 处理: 连接来源不可信 → 完全忽略 X-Forwarded-For 头部
# 结果: $remote_addr = 8.8.8.8
实际场景配置
AWS ALB/ELB 环境
http {
# AWS ALB 通常使用 VPC 内网 IP
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
server {
listen 80;
access_log /var/log/nginx/access.log main;
location / {
# 应用逻辑
}
}
}
Cloudflare CDN 环境
http {
# Cloudflare IP 段(需要定期更新)
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
# 使用 Cloudflare 专用头部
real_ip_header CF-Connecting-IP;
real_ip_recursive on;
server {
listen 80;
# 获取用户国家信息(Cloudflare 提供)
location / {
add_header X-Country-Code $http_cf_ipcountry;
# 应用逻辑
}
}
}
混合环境(CDN + 负载均衡器)
http {
# CDN 层 IP 段
set_real_ip_from 103.21.244.0/22; # Cloudflare
set_real_ip_from 104.16.0.0/12; # Cloudflare
# 负载均衡器层
set_real_ip_from 10.0.0.0/8; # AWS VPC
set_real_ip_from 172.31.0.0/16; # AWS Default VPC
# 内网代理层
set_real_ip_from 192.168.100.0/24; # 内网代理
real_ip_header X-Forwarded-For;
real_ip_recursive on;
# 详细日志格式
log_format detailed '$realip_remote_addr forwarded $remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" "$http_x_forwarded_for"';
server {
listen 80;
access_log /var/log/nginx/access.log detailed;
location / {
# 应用逻辑
}
}
}
内网多层代理环境
http {
# 第一层:外部负载均衡器
set_real_ip_from 172.16.1.0/24;
# 第二层:内网负载均衡器
set_real_ip_from 10.0.1.0/24;
# 第三层:应用网关
set_real_ip_from 192.168.1.0/24;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
server {
listen 80;
# 基于真实 IP 的访问控制
location /admin {
allow 192.168.0.0/16; # 允许内网访问
allow 10.0.0.0/8; # 允许 VPN 访问
deny all;
# 管理接口
}
# 基于真实 IP 的限流
location /api {
limit_req zone=per_ip burst=10 nodelay;
# API 接口
}
}
}
# 限流配置
limit_req_zone $remote_addr zone=per_ip:10m rate=10r/s;
多值处理算法
算法实现伪代码
def nginx_select_real_ip(forwarded_for_header, trusted_proxies, recursive_mode, connection_ip):
# 首先检查连接来源是否可信
if connection_ip not in trusted_proxies:
return connection_ip # 不可信连接,直接返回连接 IP
# 解析 X-Forwarded-For 头部
if not forwarded_for_header:
return connection_ip
ips = [ip.strip() for ip in forwarded_for_header.split(',')]
if not recursive_mode:
# 非递归模式:返回最后一个 IP
return ips[-1]
else:
# 递归模式:从右到左找第一个不可信的 IP
for ip in reversed(ips):
if ip not in trusted_proxies:
return ip
# 如果所有 IP 都是可信的,返回最左边的 IP
return ips[0]
处理示例
示例 1:标准多层代理
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 103.21.244.0/22;
real_ip_recursive on;
这样配置后,一个实际的举例以及处理过程如下:
连接来源: 10.0.1.100
X-Forwarded-For: 1.2.3.4, 103.21.244.50, 172.16.1.10, 10.0.1.100
处理步骤:
1. 连接来源 10.0.1.100 ∈ 10.0.0.0/8 → 可信 ✓
2. 从右到左检查:
- 10.0.1.100 ∈ 可信列表 → 跳过
- 172.16.1.10 ∈ 可信列表 → 跳过
- 103.21.244.50 ∈ 可信列表 → 跳过
- 1.2.3.4 ∉ 可信列表 → 选择 ✓
结果: $remote_addr = 1.2.3.4
示例 2:恶意请求检测
连接来源: 8.8.8.8 (不在可信列表中)
X-Forwarded-For: 1.2.3.4, 10.0.1.100
处理步骤:
1. 连接来源 8.8.8.8 ∉ 任何可信网段 → 不可信 ✗
2. 忽略 X-Forwarded-For 头部
结果: $remote_addr = 8.8.8.8
示例 3:边界情况 - 所有 IP 都可信
连接来源: 10.0.1.100
X-Forwarded-For: 10.0.2.50, 172.16.1.10, 10.0.1.100
处理步骤:
1. 连接来源可信 ✓
2. 从右到左检查:
- 10.0.1.100 ∈ 可信列表 → 跳过
- 172.16.1.10 ∈ 可信列表 → 跳过
- 10.0.2.50 ∈ 可信列表 → 跳过
3. 所有 IP 都可信,选择最左边的
结果: $remote_addr = 10.0.2.50
安全考虑
白名单精确性
# 好的做法:精确指定
set_real_ip_from 10.0.1.100; # 特定负载均衡器
set_real_ip_from 172.16.1.0/24; # 特定代理网段
# 危险的做法:范围过大
set_real_ip_from 0.0.0.0/0; # 信任所有 IP - 危险做法
定期更新 IP 列表
CDN 和云服务提供商的 IP 段会定期变化,需要及时更新:
#!/bin/bash
# 自动更新 Cloudflare IP 段的脚本
# 获取最新的 Cloudflare IP 段
curl -s https://www.cloudflare.com/ips-v4 > /tmp/cloudflare-ips-v4.txt
curl -s https://www.cloudflare.com/ips-v6 > /tmp/cloudflare-ips-v6.txt
# 生成 Nginx 配置片段
echo "# Cloudflare IP ranges - Updated $(date)" > /etc/nginx/conf.d/cloudflare-ips.conf
while read ip; do
echo "set_real_ip_from $ip;" >> /etc/nginx/conf.d/cloudflare-ips.conf
done < /tmp/cloudflare-ips-v4.txt
echo "real_ip_header CF-Connecting-IP;" >> /etc/nginx/conf.d/cloudflare-ips.conf
# 测试配置并重载
nginx -t && systemctl reload nginx
多层验证策略
http {
# 基础 IP 验证
set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
# 地理位置验证(需要 GeoIP 模块)
geoip_country /usr/share/GeoIP/GeoIP.dat;
server {
listen 80;
# 结合真实 IP 和地理位置的访问控制
location /admin {
# 只允许来自特定国家的特定 IP 段
if ($geoip_country_code != CN) {
return 403;
}
allow 192.168.0.0/16;
deny all;
}
# 异常检测:如果 X-Forwarded-For 和连接 IP 差异过大
location /sensitive {
access_by_lua_block {
local forwarded = ngx.var.http_x_forwarded_for
local remote = ngx.var.remote_addr
local connection = ngx.var.realip_remote_addr
-- 如果存在可疑的 IP 跳转,记录日志
if forwarded and remote ~= connection then
ngx.log(ngx.WARN, "Suspicious IP forwarding: " ..
"connection=" .. connection ..
" forwarded=" .. forwarded ..
" final=" .. remote)
end
}
}
}
}
简化方案:自定义真实 IP 头部
问题分析
X-Forwarded-For
的复杂匹配逻辑确实容易出错,特别是在多层代理环境中,我们经常听到一个叫做 HTTP头部伪造攻击 的安全问题,因为攻击者可以伪造 X-Forwarded-For
头部来绕过IP限制
一个更简单可靠的方案是:在架构最外层(如 WAF、CDN)设置自定义头部。
解决方案
方案设计
外层设置(WAF/CDN 配置)
# 在 WAF 或最外层代理设置
set $client_real_ip $remote_addr; # 获取四层握手的真实 IP
proxy_set_header X-Client-True-IP $client_real_ip;
proxy_set_header Host $host;
proxy_pass http://backend;
Nginx 端配置(极简)
# 简化配置:只信任自定义头部
set_real_ip_from 10.0.0.0/8; # 内网代理段
set_real_ip_from 172.16.0.0/12; # 负载均衡器段
real_ip_header X-Client-True-IP; # 使用自定义头部
real_ip_recursive off; # 无需递归处理
方案对比
传统 X-Forwarded-For 方案
# 复杂配置
set_real_ip_from 103.21.244.0/22; # CDN IP段1
set_real_ip_from 103.22.200.0/22; # CDN IP段2
set_real_ip_from 172.16.0.0/12; # 负载均衡器
set_real_ip_from 10.0.0.0/8; # 内网段
real_ip_header X-Forwarded-For;
real_ip_recursive on; # 需要递归处理
# 潜在问题:
# - 需要维护多个可信 IP 段
# - 复杂的递归匹配逻辑
# - CDN IP 段变化需要更新