Java 获取本机 IP 与 Web 客户端 IP:从 InetAddress 到反向代理的最佳实践

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-Forclient, 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. 小结

  • 本机 IPInetAddress.getLocalHost() 简单,但在容器/多网卡下不稳。枚举网卡更靠谱。

  • Web 客户端 IP首选 getRemoteAddr();有反向代理时配合 X-Forwarded-For 等头,且只信任自家代理

  • Spring Boot/Tomcat:开启 forward headers 支持,限制受信任代理。

  • Nginx:正确透传 X-Forwarded-For / X-Real-IP

  • 安全:警惕请求头伪造;未受信任环境不要用头部覆盖真实 IP

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

00nirvana00

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值