欢迎光临
我们一直在努力

Nginx- ngx_http_realip_module:获取客户端真实 IP 配置

在这里插入图片描述

👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。 🎯 本文将围绕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,不是为了炫技,而是为了责任。

每一个请求背后,都是一个真实的人。

你有责任,知道他是谁。

🌍 从今天起,别再让代理欺骗你的眼睛。

你,已经准备好看见真相了。


🙌 感谢你读到这里! 🔍 技术之路没有捷径,但每一次阅读、思考和实践,都在悄悄拉近你与目标的距离。 💡 如果本文对你有帮助,不妨 👍 点赞、📌 收藏、📤 分享 给更多需要的朋友! 💬 欢迎在评论区留下你的想法、疑问或建议,我会一一回复,我们一起交流、共同成长 🌿 🔔 关注我,不错过下一篇干货!我们下期再见!✨

赞(0)
未经允许不得转载:171主机测评 » Nginx- ngx_http_realip_module:获取客户端真实 IP 配置
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址