
👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。 🎯 本文将围绕Nginx这个话题展开,希望能为你带来一些启发或实用的参考。 🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获!
文章目录
- Nginx – ngx_http_realip_module:获取客户端真实 IP 配置 🌐
-
- 为什么需要真实 IP?💡
-
- 常见问题场景:
- Nginx 的 ngx_http_realip_module 模块简介 🛠️
-
- 支持的头字段:
- 实战配置:从零搭建真实 IP 获取链路 🚀
-
- 第一步:在 Nginx 中启用 realip 模块
- 第二步:配置 Nginx 反向代理
- 第三步:配置 Java 应用接收真实 IP
- Java Spring Boot 配置:信任代理头 🐘
-
- 方案一:使用 Spring Boot 内置支持(推荐)
- 方案二:使用 Tomcat 自定义 Valve(适用于传统部署)
- 方案三:自定义 Filter(最灵活,适用于任何容器)
- 测试真实 IP 是否生效 🧪
-
- 测试方法一:通过 Java Controller 打印
- 测试方法二:使用 curl 模拟请求
- 为什么不能直接信任 X-Forwarded-For?🚫
-
- 攻击示例:
- 正确做法:
- 与 CDN 和云服务商的兼容性 🌩️
- Mermaid 图表:真实 IP 传递流程 📈
- 高级技巧:记录原始 IP 与真实 IP 双重日志 📜
- Java 中的 IP 地理定位实战 🌍
-
- 使用 MaxMind GeoIP2(推荐)
-
- Maven 依赖:
- 下载数据库:
- Java 代码:
- 在 Controller 中使用:
- 安全加固建议 ✅
-
- 示例:IP 限流(基于真实 IP)
- 常见错误排查 🛠️
-
- 调试命令:
- 企业级最佳实践总结 🏆
- 结语:真实 IP 是安全与洞察的基石 🔑
- 附录:完整 Nginx 配置模板(可直接使用)
- 附录:Java 完整配置示例(application.yml)
- 最后:记住这句话 🧠
Nginx – ngx_http_realip_module:获取客户端真实 IP 配置 🌐
在现代互联网架构中,客户端请求往往经过多层代理、负载均衡器、CDN、反向代理等中间节点。当这些请求最终到达你的后端应用服务器(如 Java Web 应用)时,你看到的“客户端 IP”可能不再是真实的用户 IP,而是中间代理服务器的地址。这在日志分析、安全风控、地域统计、访问频率限制等场景中会造成严重误导。
例如:你看到 1000 个请求都来自 192.168.1.10,你以为是单个用户疯狂刷接口,实际上这可能是你的 Nginx 负载均衡器的内网 IP,背后可能有成千上万的真实用户。
这时候,ngx_http_realip_module 就是你必须掌握的“真相之眼” 👁️🗨️。
为什么需要真实 IP?💡
在分布式系统中,一个典型的请求路径可能是:
[用户浏览器] → [Cloudflare CDN] → [AWS ALB] → [Nginx 反向代理] → [Java Spring Boot 应用]
在 Java 应用中,你可能通过 HttpServletRequest.getRemoteAddr() 获取客户端 IP:
String clientIp = request.getRemoteAddr();
System.out.println("Client IP: " + clientIp);
// 输出:172.16.0.5(这是 Nginx 的内网 IP,不是真实用户 IP)
这显然不是我们想要的结果。我们真正关心的是:
“这个请求,到底是谁发的?”
常见问题场景:
- 🔐 安全风控:识别刷单、撞库、DDoS 攻击源
- 📊 数据分析:按地域统计用户活跃度
- 🚫 访问限制:限制单个 IP 每分钟请求次数
- 🌍 地理定位:显示用户所在城市、国家
- 📝 日志审计:追踪用户行为轨迹
如果没有真实 IP,以上所有功能都形同虚设。
Nginx 的 ngx_http_realip_module 模块简介 🛠️
ngx_http_realip_module 是 Nginx 的一个标准模块(默认编译时启用),用于替换客户端 IP 地址,使其从 HTTP 请求头中提取真实来源 IP。
它主要依赖两个指令:
- set_real_ip_from:指定信任的代理服务器 IP 或 CIDR 范围
- real_ip_header:指定从哪个 HTTP 头中提取真实 IP(如 X-Real-IP、X-Forwarded-For)
✅ 重要提示:该模块只对信任的代理服务器生效。如果你不配置 set_real_ip_from,Nginx 会忽略所有头信息,防止 IP 欺骗。
支持的头字段:
| X-Real-IP | 最常见,由单层代理设置,仅包含一个 IP |
| X-Forwarded-For | 多层代理时使用,是一个 IP 列表,格式:client, proxy1, proxy2 |
| True-Client-IP | Cloudflare 等 CDN 使用的自定义头 |
🌐 Cloudflare 官方文档 中说明了其对 True-Client-IP 的支持。
实战配置:从零搭建真实 IP 获取链路 🚀
我们以一个典型企业级架构为例:
[真实用户] → [Cloudflare] → [Nginx (负载均衡)] → [Java Spring Boot 应用]
第一步:在 Nginx 中启用 realip 模块
确保你的 Nginx 编译时包含 –with-http_realip_module(大多数发行版默认开启):
nginx -V 2>&1 | grep realip
# 输出应包含:–with-http_realip_module
如果没有,请重新编译或更换包管理器版本(如 Ubuntu 的 nginx-extras)。
第二步:配置 Nginx 反向代理
# /etc/nginx/nginx.conf 或站点配置文件
http {
# 设置信任的代理服务器(Cloudflare 的 IP 段)
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 104.16.0.0/12;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2c0f:f248::/32;
set_real_ip_from 2a06:98c0::/29;
# 使用 Cloudflare 提供的 True-Client-IP 头
real_ip_header True-Client-IP;
# 如果 Cloudflare 未设置 True-Client-IP,降级使用 X-Forwarded-For
real_ip_recursive on;
# 可选:记录原始 IP 到日志
log_format main '$realip_remote_addr – $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
access_log /var/log/nginx/access.log main;
}
📌 为什么用 True-Client-IP? Cloudflare 在其文档中明确推荐使用 True-Client-IP,因为它更可靠,且不会被伪造。 Cloudflare 官方说明
📌 real_ip_recursive on; 是什么? 如果你设置了 X-Forwarded-For: 1.2.3.4, 5.6.7.8, 9.10.11.12,其中 9.10.11.12 是你信任的代理,那么开启 recursive 后,Nginx 会从右往左查找第一个非信任 IP,即 1.2.3.4 作为真实客户端 IP。 关闭则只取第一个 IP(1.2.3.4),即使它也是代理。
第三步:配置 Java 应用接收真实 IP
现在,Nginx 已经将真实 IP 填入 X-Real-IP 或 True-Client-IP 头。但 Java Servlet 容器(Tomcat、Undertow、Netty)默认不会信任这些头!
你需要配置你的 Web 服务器,使其信任 Nginx 传递的头信息。
Java Spring Boot 配置:信任代理头 🐘
方案一:使用 Spring Boot 内置支持(推荐)
Spring Boot 2.3+ 提供了内置的 server.forward-headers-strategy 配置,无需额外依赖。
# application.yml
server:
forward-headers-strategy: framework
tomcat:
remote-ip-header: X–Forwarded–For
protocol-header: X–Forwarded–Proto
internal-proxies: 10\\.0\\.0\\.0/8|172\\.16\\.0\\.0/12|192\\.168\\.0\\.0/16|127\\.0\\.0\\.1
# 如果你使用 Cloudflare,添加其 IP 段
# internal-proxies: 10\\.0\\.0\\.0/8|172\\.16\\.0\\.0/12|192\\.168\\.0\\.0/16|127\\.0\\.0\\.1|173\\.245\\.48\\.0/20|103\\.21\\.244\\.0/22|103\\.22\\.200\\.0/22|103\\.31\\.4\\.0/22|104\\.16\\.0\\.0/12|108\\.162\\.192\\.0/18|131\\.0\\.72\\.0/22|141\\.101\\.64\\.0/18|162\\.158\\.0\\.0/15|172\\.64\\.0\\.0/13|188\\.114\\.96\\.0/20|190\\.93\\.240\\.0/20|197\\.234\\.240\\.0/22|198\\.41\\.128\\.0/17|2400\\:cb00\\:\\:/32|2606\\:4700\\:\\:/32|2803\\:f800\\:\\:/32|2405\\:b500\\:\\:/32|2405\\:8100\\:\\:/32|2c0f\\:f248\\:\\:/32|2a06\\:98c0\\:\\:/29
💡 forward-headers-strategy: framework 表示使用 Spring 框架内置的 RemoteIpValve 来处理代理头,自动重写 getRemoteAddr()、getScheme()、isSecure() 等方法。
方案二:使用 Tomcat 自定义 Valve(适用于传统部署)
如果你使用的是传统 WAR 部署到独立 Tomcat,可以配置 server.xml:
<Engine name="Catalina" defaultHost="localhost">
<Valve className="org.apache.catalina.valves.RemoteIpValve"
remoteIpHeader="X-Forwarded-For"
proxiesHeader="X-Forwarded-By"
protocolHeader="X-Forwarded-Proto"
internalProxies="10\\.0\\.0\\.0/8|172\\.16\\.0\\.0/12|192\\.168\\.0\\.0/16|127\\.0\\.0\\.1|173\\.245\\.48\\.0/20|103\\.21\\.244\\.0/22|103\\.22\\.200\\.0/22|103\\.31\\.4\\.0/22|104\\.16\\.0\\.0/12|108\\.162\\.192\\.0/18|131\\.0\\.72\\.0/22|141\\.101\\.64\\.0/18|162\\.158\\.0\\.0/15|172\\.64\\.0\\.0/13|188\\.114\\.96\\.0/20|190\\.93\\.240\\.0/20|197\\.234\\.240\\.0/22|198\\.41\\.128\\.0/17|2400\\:cb00\\:\\:/32|2606\\:4700\\:\\:/32|2803\\:f800\\:\\:/32|2405\\:b500\\:\\:/32|2405\\:8100\\:\\:/32|2c0f\\:f248\\:\\:/32|2a06\\:98c0\\:\\:/29"
clientIpHeader="X-Real-IP"
requestAttributesEnabled="true" />
</Engine>
⚠️ 注意:internalProxies 必须使用正则表达式,且 IP 段需转义 . 为 \\.,: 为 \\:。
方案三:自定义 Filter(最灵活,适用于任何容器)
如果你不想依赖框架,或使用的是 Undertow、Netty、Vert.x 等非标准容器,可以自己写一个 Filter:
package com.example.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@WebFilter("/*")
public class RealIpFilter implements Filter {
// 信任的代理 IP 段(与 Nginx 一致)
private static final Set<String> TRUSTED_PROXY_IPS = new HashSet<>(
Arrays.asList(
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"127.0.0.1",
"173.245.48.0/20",
"103.21.244.0/22",
"103.22.200.0/22",
"103.31.4.0/22",
"104.16.0.0/12",
"108.162.192.0/18",
"131.0.72.0/22",
"141.101.64.0/18",
"162.158.0.0/15",
"172.64.0.0/13",
"188.114.96.0/20",
"190.93.240.0/20",
"197.234.240.0/22",
"198.41.128.0/17",
"2400:cb00::/32",
"2606:4700::/32",
"2803:f800::/32",
"2405:b500::/32",
"2405:8100::/32",
"2c0f:f248::/32",
"2a06:98c0::/29"
)
);
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (request instanceof HttpServletRequest httpRequest) {
String clientIp = extractRealIp(httpRequest);
if (clientIp != null) {
// 将真实 IP 设置为请求属性,供后续使用
httpRequest.setAttribute("realClientIp", clientIp);
// 可选:覆盖原始 getRemoteAddr() 的行为(需包装)
HttpServletRequestWrapper wrappedRequest = new HttpServletRequestWrapper(httpRequest) {
@Override
public String getRemoteAddr() {
return clientIp;
}
};
chain.doFilter(wrappedRequest, response);
return;
}
}
chain.doFilter(request, response);
}
private String extractRealIp(HttpServletRequest request) {
// 优先使用 Cloudflare 的 True-Client-IP
String ip = request.getHeader("True-Client-IP");
if (isTrustedProxy(request) && isValidIp(ip)) {
return ip;
}
// 次选 X-Forwarded-For
ip = request.getHeader("X-Forwarded-For");
if (isTrustedProxy(request) && ip != null) {
// X-Forwarded-For: client, proxy1, proxy2
// 取第一个非信任 IP
String[] ips = ip.split(",");
for (String candidate : ips) {
String trimmed = candidate.trim();
if (isValidIp(trimmed) && !isTrustedProxyIp(trimmed)) {
return trimmed;
}
}
}
// 再次尝试 X-Real-IP
ip = request.getHeader("X-Real-IP");
if (isTrustedProxy(request) && isValidIp(ip)) {
return ip;
}
// 最后回退
return request.getRemoteAddr();
}
private boolean isTrustedProxy(HttpServletRequest request) {
String proxyIp = request.getRemoteAddr();
return TRUSTED_PROXY_IPS.stream().anyMatch(cidr -> {
try {
return IpUtils.isInNetwork(proxyIp, cidr);
} catch (Exception e) {
return false;
}
});
}
private boolean isValidIp(String ip) {
if (ip == null || ip.isEmpty()) return false;
return ip.matches("^([0-9]{1,3}\\\\.){3}[0-9]{1,3}$") ||
ip.matches("^([0-9a-fA-F:]+)$");
}
private boolean isTrustedProxyIp(String ip) {
return TRUSTED_PROXY_IPS.stream().anyMatch(cidr -> {
try {
return IpUtils.isInNetwork(ip, cidr);
} catch (Exception e) {
return false;
}
});
}
}
⚠️ 上面代码中使用了 IpUtils 工具类,我们来实现它:
package com.example.filter;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.regex.Pattern;
public class IpUtils {
private static final Pattern IPV4_PATTERN = Pattern.compile(
"^([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])\\\\." +
"([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])\\\\." +
"([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])\\\\." +
"([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])$");
private static final Pattern IPV6_PATTERN = Pattern.compile(
"^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$");
public static boolean isInNetwork(String ip, String cidr) {
try {
if (cidr.contains("/")) {
String[] parts = cidr.split("/", 2);
String network = parts[0];
int prefixLength = Integer.parseInt(parts[1]);
InetAddress ipAddr = InetAddress.getByName(ip);
InetAddress netAddr = InetAddress.getByName(network);
if (ipAddr instanceof java.net.Inet4Address && netAddr instanceof java.net.Inet4Address) {
byte[] ipBytes = ipAddr.getAddress();
byte[] netBytes = netAddr.getAddress();
byte[] mask = new byte[4];
for (int i = 0; i < 4; i++) {
if (prefixLength >= 8) {
mask[i] = (byte) 0xFF;
prefixLength -= 8;
} else {
mask[i] = (byte) (0xFF << (8 – prefixLength));
prefixLength = 0;
}
}
for (int i = 0; i < 4; i++) {
if ((ipBytes[i] & mask[i]) != (netBytes[i] & mask[i])) {
return false;
}
}
return true;
}
}
} catch (Exception ignored) {}
return false;
}
public static boolean isIPv4(String ip) {
return IPV4_PATTERN.matcher(ip).matches();
}
public static boolean isIPv6(String ip) {
return IPV6_PATTERN.matcher(ip).matches();
}
}
✅ 这个方案完全独立于框架,适用于任何 Java Web 容器,包括 Undertow、Jetty、Netty 等。
测试真实 IP 是否生效 🧪
测试方法一:通过 Java Controller 打印
package com.example.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
@RestController
public class IpController {
@GetMapping("/ip")
public String getClientIp(HttpServletRequest request) {
String remoteAddr = request.getRemoteAddr();
String realIp = (String) request.getAttribute("realClientIp");
String xRealIp = request.getHeader("X-Real-IP");
String xForwardedFor = request.getHeader("X-Forwarded-For");
String trueClientIp = request.getHeader("True-Client-IP");
return String.format(
"✅ Real IP (from getRemoteAddr): %s%n" +
"🎯 Extracted Real IP (from attribute): %s%n" +
"🔧 X-Real-IP Header: %s%n" +
"🌐 X-Forwarded-For Header: %s%n" +
"☁️ True-Client-IP Header: %s%n",
remoteAddr,
realIp != null ? realIp : "N/A",
xRealIp != null ? xRealIp : "N/A",
xForwardedFor != null ? xForwardedFor : "N/A",
trueClientIp != null ? trueClientIp : "N/A"
);
}
}
访问 /ip 接口,你应该看到:
✅ Real IP (from getRemoteAddr): 1.2.3.4
🎯 Extracted Real IP (from attribute): 1.2.3.4
🔧 X-Real-IP Header: 1.2.3.4
🌐 X-Forwarded-For Header: 1.2.3.4, 173.245.48.10
☁️ True-Client-IP Header: 1.2.3.4
✅ 说明:getRemoteAddr() 已被正确替换为真实 IP!
测试方法二:使用 curl 模拟请求
curl -H "X-Forwarded-For: 8.8.8.8, 173.245.48.10" \\
-H "X-Real-IP: 8.8.8.8" \\
-H "True-Client-IP: 8.8.8.8" \\
http://your-domain.com/ip
如果 Nginx 配置了 set_real_ip_from 173.245.48.10,那么 8.8.8.8 就会被识别为真实客户端 IP。
为什么不能直接信任 X-Forwarded-For?🚫
很多开发者图省事,直接写:
String ip = request.getHeader("X-Forwarded-For");
这是极其危险的行为!
攻击示例:
curl -H "X-Forwarded-For: 1.1.1.1, 127.0.0.1" http://your-app.com
如果后端直接取第一个 IP,就会误认为用户是 1.1.1.1,而实际上请求来自本地代理(127.0.0.1),攻击者可能伪装成其他 IP 进行绕过。
正确做法:
- 只信任已知的、受控的代理服务器
- 从 X-Forwarded-For 列表中从右往左找第一个非信任 IP
- 优先使用 True-Client-IP(如 Cloudflare)
🔐 安全原则:Never trust client headers directly. Always validate the source.
与 CDN 和云服务商的兼容性 🌩️
| Cloudflare | True-Client-IP | 最可靠,官方推荐 |
| AWS ALB | X-Forwarded-For | 会追加多个 IP,需递归解析 |
| Azure Front Door | X-Forwarded-For | 与 ALB 类似 |
| Akamai | X-Forwarded-For | 通常设置 X-Forwarded-For 和 X-Forwarded-Host |
| Fastly | X-Forwarded-For | 支持 Fastly-Client-IP |
| BunnyCDN | X-Forwarded-For | 也支持 X-Real-IP |
📚 AWS ALB 官方文档
📚 Azure Front Door 头信息
📚 Fastly 客户端 IP 头
Mermaid 图表:真实 IP 传递流程 📈
渲染错误: Mermaid 渲染失败: Parse error on line 5: … D –>|getRemoteAddr() 返回 203.0.113.45| ———————–^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'
📌 图中可见:真实 IP 从用户 → CDN → Nginx → Java 应用,全程被正确传递并替换。
高级技巧:记录原始 IP 与真实 IP 双重日志 📜
有时候,你不仅需要真实 IP,还需要知道“经过了哪些代理”。
Nginx 可以同时记录原始 IP 和真实 IP:
log_format detailed '$remote_addr [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'real_ip=$realip_remote_addr '
'x_forwarded_for="$http_x_forwarded_for" '
'true_client_ip="$http_true_client_ip"';
access_log /var/log/nginx/access.log detailed;
日志样例:
173.245.48.10 [15/Apr/2024:10:23:45 +0000] "GET /api/user HTTP/1.1" 200 1234 "-" "Mozilla/5.0"
real_ip=203.0.113.45 x_forwarded_for="203.0.113.45, 173.245.48.10" true_client_ip=203.0.113.45
这样,运维人员可以:
- 快速定位攻击源(真实 IP)
- 分析代理链(X-Forwarded-For)
- 审计 CDN 是否正常转发
Java 中的 IP 地理定位实战 🌍
获取真实 IP 后,你还可以做地理定位,比如显示用户所在国家。
使用 MaxMind GeoIP2(推荐)
Maven 依赖:
<dependency>
<groupId>com.maxmind.geoip2</groupId>
<artifactId>geoip2</artifactId>
<version>2.16.0</version>
</dependency>
下载数据库:
前往 MaxMind GeoLite2 下载 GeoLite2-City.mmdb(免费版)。
Java 代码:
package com.example.geo;
import com.maxmind.geoip2.DatabaseReader;
import com.maxmind.geoip2.model.CityResponse;
import com.maxmind.geoip2.record.City;
import com.maxmind.geoip2.record.Country;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
@Service
public class GeoIpService {
private final DatabaseReader reader;
public GeoIpService(@Value("${geoip.db-path}") String dbPath) throws IOException {
this.reader = new DatabaseReader.Builder(new File(dbPath)).build();
}
public String getCountryCode(String ipAddress) {
try {
InetAddress ip = InetAddress.getByName(ipAddress);
CityResponse response = reader.city(ip);
Country country = response.getCountry();
return country.getIsoCode();
} catch (Exception e) {
return "UNKNOWN";
}
}
public String getCity(String ipAddress) {
try {
InetAddress ip = InetAddress.getByName(ipAddress);
CityResponse response = reader.city(ip);
City city = response.getCity();
return city.getName() != null ? city.getName() : "Unknown";
} catch (Exception e) {
return "Unknown";
}
}
}
在 Controller 中使用:
@GetMapping("/geo")
public String getGeoInfo(HttpServletRequest request, GeoIpService geoIpService) {
String realIp = (String) request.getAttribute("realClientIp");
if (realIp == null) realIp = request.getRemoteAddr();
String country = geoIpService.getCountryCode(realIp);
String city = geoIpService.getCity(realIp);
return String.format("🌍 您来自:%s, %s (IP: %s)", city, country, realIp);
}
🌐 MaxMind GeoLite2 免费数据库下载
⚠️ 注意:免费版更新频率为每月一次,精度略低。商业版精度更高,适合生产环境。
安全加固建议 ✅
| ❌ 伪造 X-Forwarded-For | 仅在信任的代理后启用 realip,否则忽略 |
| ❌ 开放公网代理 | 不要让 Nginx 接收来自公网的 X-Real-IP |
| ❌ 未校验 IP 范围 | 必须配置 set_real_ip_from,限制为 CDN/负载均衡 IP |
| ❌ 日志泄露真实 IP | 生产环境避免在日志中打印完整 IP,可脱敏(如 192.168.*.*) |
| ❌ 无访问频率限制 | 即使有真实 IP,也要配合 Redis 做限流(如令牌桶) |
示例:IP 限流(基于真实 IP)
@RestController
public class RateLimitController {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@GetMapping("/api/data")
public String getData(HttpServletRequest request) {
String realIp = (String) request.getAttribute("realClientIp");
if (realIp == null) realIp = request.getRemoteAddr();
String key = "rate_limit:" + realIp;
Long count = redisTemplate.opsForValue().increment(key, 1);
if (count == 1) {
redisTemplate.expire(key, 60, TimeUnit.SECONDS);
}
if (count > 100) {
return "Too many requests. Try again in 1 minute.";
}
return "Data retrieved successfully.";
}
}
✅ 使用 Redis 实现分布式限流,结合真实 IP,精准控制每个用户访问频率。
常见错误排查 🛠️
| getRemoteAddr() 仍是代理 IP | Nginx 未配置 set_real_ip_from | 检查是否包含 CDN 的 IP 段 |
| X-Forwarded-For 为空 | CDN 未设置头 | 检查 CDN 配置,启用“传递客户端 IP” |
| 多层代理解析错误 | real_ip_recursive off | 改为 on |
| IPv6 地址被忽略 | Nginx 未配置 IPv6 信任段 | 添加 set_real_ip_from 2001:db8::/32; |
| Java 应用不识别头 | 未设置 forward-headers-strategy | Spring Boot 2.3+ 必须配置 |
| 日志中显示 ::1 | 客户端是本地回环 | 检查是否在测试环境使用 curl localhost |
调试命令:
# 查看 Nginx 配置是否生效
nginx -t
# 查看当前请求头(模拟)
curl -H "True-Client-IP: 8.8.8.8" -v http://localhost
# 查看 Nginx 日志
tail -f /var/log/nginx/access.log
# 查看 Java 应用日志
journalctl -u your-spring-app -f
企业级最佳实践总结 🏆
| Nginx 配置 | 使用 True-Client-IP + real_ip_recursive on + 信任 CDN IP 段 |
| Java 应用 | Spring Boot 2.3+ 使用 server.forward-headers-strategy: framework |
| 日志记录 | 同时记录 realip_remote_addr 和 http_x_forwarded_for |
| 安全防护 | 禁止公网直接访问后端,仅允许 CDN/负载均衡访问 |
| 地理定位 | 使用 MaxMind GeoIP2 + 本地数据库,避免网络请求延迟 |
| 限流控制 | 基于真实 IP + Redis 实现分布式限流 |
| 监控告警 | 对异常 IP(如频繁更换、来自高危国家)做实时告警 |
| 测试验证 | 使用 curl -H "True-Client-IP: 1.2.3.4" 模拟攻击测试 |
结语:真实 IP 是安全与洞察的基石 🔑
在现代 Web 架构中,“获取真实客户端 IP” 不是一个可选项,而是一个必须完成的基础工程。它关系到:
- 用户行为分析的准确性
- 安全防御的有效性
- 合规审计的完整性
- 商业决策的可靠性
不要因为“看起来能用”就忽略它。 不要因为“别人没配”就觉得自己也不用配。
🌐 互联网的本质是连接,但连接的前提是可识别、可追溯、可信任。
配置好 ngx_http_realip_module,你就为你的系统装上了一双“看穿伪装的眼睛”。
从此,不再被代理的迷雾遮蔽双眼。
从此,每一个请求,都有迹可循。
附录:完整 Nginx 配置模板(可直接使用)
# /etc/nginx/conf.d/realip.conf
# 信任的代理 IP(Cloudflare + AWS ALB + 自建负载均衡)
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;
set_real_ip_from 127.0.0.1;
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 104.16.0.0/12;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2c0f:f248::/32;
set_real_ip_from 2a06:98c0::/29;
# 使用 Cloudflare 推荐的头
real_ip_header True-Client-IP;
real_ip_recursive on;
# 日志格式包含真实 IP
log_format main '$realip_remote_addr – $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'forwarded_for="$http_x_forwarded_for" '
'true_client_ip="$http_true_client_ip"';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;
附录:Java 完整配置示例(application.yml)
server:
forward-headers-strategy: framework
tomcat:
remote-ip-header: X–Forwarded–For
protocol-header: X–Forwarded–Proto
internal-proxies: 10\\.0\\.0\\.0/8|172\\.16\\.0\\.0/12|192\\.168\\.0\\.0/16|127\\.0\\.0\\.1|173\\.245\\.48\\.0/20|103\\.21\\.244\\.0/22|103\\.22\\.200\\.0/22|103\\.31\\.4\\.0/22|104\\.16\\.0\\.0/12|108\\.162\\.192\\.0/18|131\\.0\\.72\\.0/22|141\\.101\\.64\\.0/18|162\\.158\\.0\\.0/15|172\\.64\\.0\\.0/13|188\\.114\\.96\\.0/20|190\\.93\\.240\\.0/20|197\\.234\\.240\\.0/22|198\\.41\\.128\\.0/17|2400\\:cb00\\:\\:/32|2606\\:4700\\:\\:/32|2803\\:f800\\:\\:/32|2405\\:b500\\:\\:/32|2405\\:8100\\:\\:/32|2c0f\\:f248\\:\\:/32|2a06\\:98c0\\:\\:/29
address-header: X–Forwarded–Host
scheme-header: X–Forwarded–Proto
spring:
application:
name: real–ip–demo
# GeoIP 数据库路径
geoip:
db-path: /opt/geoip/GeoLite2–City.mmdb
最后:记住这句话 🧠
“你看到的 IP,不一定是真实的。但你配置对了,就能看到真相。”
配置 ngx_http_realip_module,不是为了炫技,而是为了责任。
每一个请求背后,都是一个真实的人。
你有责任,知道他是谁。
🌍 从今天起,别再让代理欺骗你的眼睛。
你,已经准备好看见真相了。
🙌 感谢你读到这里! 🔍 技术之路没有捷径,但每一次阅读、思考和实践,都在悄悄拉近你与目标的距离。 💡 如果本文对你有帮助,不妨 👍 点赞、📌 收藏、📤 分享 给更多需要的朋友! 💬 欢迎在评论区留下你的想法、疑问或建议,我会一一回复,我们一起交流、共同成长 🌿 🔔 关注我,不错过下一篇干货!我们下期再见!✨
![[项目] --- 基于 Spring Boot + Redis的多设计模式抽奖系统-171主机测评](https://www.171host.com/wp-content/uploads/2026/11/20261031220757-6ae666bdaa3f5-220x150.png)


