1. 本机 IP 的基础写法与常见坑
入门写法:
public static String getIp() {
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
return "";
}
}
常见坑:
-
可能拿到的是 127.0.0.1/::1(回环地址);
-
在 Docker/K8s 或多网卡机器上,返回的是容器主机名解析的地址,不是你期望的对外网卡;
-
机器没正确配置
/etc/hosts或 DNS 时会抛UnknownHostException; -
想要 IPv4,结果拿到 IPv6。
更稳健做法:枚举网卡,选择“可用、非回环、非虚拟”的首个 IPv4”
import java.net.*;
import java.util.*;
public final class NetUtil {
/** 更稳健地获取本机 IPv4(优先业务网卡) */
public static String getLocalIpv4() {
try {
Enumeration<NetworkInterface> ifs = NetworkInterface.getNetworkInterfaces();
while (ifs.hasMoreElements()) {
NetworkInterface nif = ifs.nextElement();
if (!nif.isUp() || nif.isLoopback() || nif.isVirtual()) continue;
Enumeration<InetAddress> addrs = nif.getInetAddresses();
while (addrs.hasMoreElements()) {
InetAddress addr = addrs.nextElement();
if (addr instanceof Inet4Address
&& !addr.isLoopbackAddress()
&& !addr.isLinkLocalAddress()
&& !addr.isAnyLocalAddress()) {
return addr.getHostAddress(); // 命中即返回
}
}
}
// 兜底
return InetAddress.getLocalHost().getHostAddress();
} catch (Exception e) {
return "127.0.0.1";
}
}
}
如果你优先想拿 内网私有地址(如 10.* / 172.16.* / 192.168.*),可以再加一层“站点本地地址优先”的判断。
2. Web 项目里获取客户端 IP:getRemoteAddr 优于 getRemoteHost
String ip = request.getRemoteHost();
问题:getRemoteHost() 可能触发 反向 DNS 查询,有性能损耗,还可能拿到主机名而不是 IP。
建议:优先使用 request.getRemoteAddr()(直接给 IP,不解析 DNS)。
在没有反向代理时,它就是客户端 IP;一旦有 Nginx / Apache / LB / CDN,客户端 IP 会被放到请求头里(如 X-Forwarded-For),直接下一节。
3. 有反向代理时的“真实客户端 IP”
3.1 请求头的约定
常见头(可能同时存在):
-
X-Forwarded-For:client, proxy1, proxy2...(取第一个就是原始客户端 IP) -
X-Real-IP:某些代理只写这个 -
Forwarded:标准写法(for=...; proto=...) -
厂商专用:
CF-Connecting-IP(Cloudflare),True-Client-IP(Akamai)等
3.2 Java 实用方法(安全+通用)
import jakarta.servlet.http.HttpServletRequest;
import java.util.*;
public final class ClientIp {
private static final String[] IP_HEADER_CANDIDATES = new String[] {
"X-Forwarded-For", "X-Real-IP", "CF-Connecting-IP",
"True-Client-IP", "X-Forwarded", "Forwarded-For", "Forwarded"
};
/** 在受信任的代理链路下获取客户端真实 IP */
public static String getClientIp(HttpServletRequest request) {
for (String h : IP_HEADER_CANDIDATES) {
String v = request.getHeader(h);
if (v == null || v.isBlank() || "unknown".equalsIgnoreCase(v)) continue;
// X-Forwarded-For 形如: "client, proxy1, proxy2"
if ("X-Forwarded-For".equalsIgnoreCase(h)) {
for (String p : v.split(",")) {
String ip = p.trim();
if (!ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
return ip;
}
}
} else {
return v.trim();
}
}
return request.getRemoteAddr();
}
}
安全提示:这些头 可以被伪造。务必只在受信任的反向代理(如自家 Nginx)已设置并覆盖这些头的前提下使用;否则回退
getRemoteAddr()。
4. Spring Boot / Tomcat 的“官方”链路处理
4.1 Spring Boot(Tomcat 内嵌)
# Spring Boot 2.6+
server.forward-headers-strategy=framework
# 旧版本:
# server.use-forward-headers=true
# 只信任内网代理(正则),防止随便来的请求自带伪造头
server.tomcat.remoteip.remote-ip-header=x-forwarded-for
server.tomcat.remoteip.protocol-header=x-forwarded-proto
server.tomcat.remoteip.internal-proxies=10\\..*|192\\.168\\..*|127\\.0\\.0\\.1
配置好后,request.getRemoteAddr() 就会是解析后的客户端 IP。
4.2 反向代理(Nginx)示例
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
5. IPv6、容器与多网卡的补充
-
IPv6:客户端可能是
::1或外网 IPv6。展示或日志中可同时记录 IPv4/IPv6;如强制 IPv4,代码中过滤Inet4Address。 -
容器/Docker:容器内的
getLocalHost()多半返回容器名解析的地址。生产上通常不靠“程序内推断对外 IP”,而是通过环境变量/配置指定服务对外绑定的地址或域名。 -
多网卡:上面“枚举网卡”的写法已经规避了回环/虚拟网卡。若机器有多条业务网卡,建议显式配置你希望绑定的网卡或 IP。
6. 调试与示例
输出本机 IP:
public static void main(String[] args) {
System.out.println("Local IPv4: " + NetUtil.getLocalIpv4());
}
在 Controller 里打印客户端 IP:
@GetMapping("/hello")
public String hello(HttpServletRequest req) {
String ip = ClientIp.getClientIp(req);
return "Hello, your IP is " + ip;
}
7. 小结
-
本机 IP:
InetAddress.getLocalHost()简单,但在容器/多网卡下不稳。枚举网卡更靠谱。 -
Web 客户端 IP:首选
getRemoteAddr();有反向代理时配合X-Forwarded-For等头,且只信任自家代理。 -
Spring Boot/Tomcat:开启 forward headers 支持,限制受信任代理。
-
Nginx:正确透传
X-Forwarded-For / X-Real-IP。 -
安全:警惕请求头伪造;未受信任环境不要用头部覆盖真实 IP。
591

被折叠的 条评论
为什么被折叠?



