Java IP 工具类实战:IpUtils 从格式校验到 SSRF 防御的完整方案

选题说明:IP 地址处理是后端开发的高频需求——日志中提取客户端 IP、接口限流白名单、子网计算、端口探测、IP 脱敏展示。JDK 的 InetAddress 功能有限,大多数项目会自己拼工具类,但往往缺少安全加固(SSRF、DNS 注入)。IpUtils 内置 13 项安全特性,覆盖 IPv4/IPv6 全场景,零外部依赖,是生产级的 IP 处理方案。


一、为什么需要统一的 IP 工具

在实际项目中,IP 处理的需求远比想象中复杂:

  1. 校验不严格:用正则校验 IP,"01.02.03.04" 通过了(前导零,可能触发八进制解析)
  2. IPv6 处理薄弱:压缩格式 ::1、带 Zone ID 的 fe80::1%eth0 不知道怎么校验
  3. CIDR 计算手写:子网掩码和 CIDR 互转,网络地址/广播地址计算,每次都翻文档
  4. 白名单/黑名单效率低:遍历 List 逐个比对,1000 条规则就卡顿
  5. 安全漏洞:端口探测接受主机名,攻击者可以传入 169.254.169.254(云元数据)做 SSRF

IpUtils 围绕这些问题做了系统性封装,零外部依赖,仅使用 JDK 标准库。

环境准备:

<!-- Spring Boot 2.x (javax) -->
<dependency>
    <groupId>com.gitee.apanlh</groupId>
    <artifactId>pan-common</artifactId>
    <version>2.0.6</version>
</dependency>

<!-- Spring Boot 3.x (jakarta) -->
<dependency>
    <groupId>com.gitee.apanlh</groupId>
    <artifactId>pan-common</artifactId>
    <version>3.0.6</version>
</dependency>

二、功能全景

分类 核心方法 说明
格式校验 isIpV4() / isIpV6() / isIp() 严格格式校验,含前导零拒绝
地址分类 isPrivateIp() / isPublicIp() / isLoopbackAddress() RFC 标准分类
数值转换 toLongByIp() / toBytes() / maskToCidr() IP ↔ long / byte[] / CIDR 互转
CIDR 子网 isInSubnet() / getNetworkAddress() / getAvailableHostCount() 子网匹配与计算
IP 段解析 matchIpRange() / countIpRange() / listIpRange() 支持 CIDR/范围/通配符/单 IP
批量匹配 matchAny() / matchNone() 白名单/黑名单校验
IDC 探测 isPortOpen() / measureLatency() / getReverseDns() 端口/延迟/PTR(SSRF 加固)
脱敏 hideIp() / hideLocalIp() IP 地址脱敏展示
本机信息 getLocalAddress() / getEffectiveAddresses() / getMacAddress() 本机 IP / MAC

三、格式校验

3.1 isIpV4 — IPv4 严格校验

isIpV4 使用 parseSegments 作为统一校验内核,内置多项安全检查:

// ========== 合法 ==========
IpUtils.isIpV4("192.168.1.1");       // true
IpUtils.isIpV4("0.0.0.0");           // true
IpUtils.isIpV4("255.255.255.255");   // true

// ========== 非法 ==========
IpUtils.isIpV4("256.1.1.1");         // false — 段值超 255
IpUtils.isIpV4("01.1.1.1");          // false — 前导零(防八进制歧义)
IpUtils.isIpV4("192.168.1");         // false — 仅 3 段
IpUtils.isIpV4("192.168.1.1.1");     // false — 5 段
IpUtils.isIpV4("192.168.1.a");       // false — 非数字
IpUtils.isIpV4(null);                // false
IpUtils.isIpV4("");                  // false

安全特性

  • 禁止前导零:"01" 非法,防止与八进制混淆
  • 段值严格 0-255
  • 必须恰好 4 段,每段 1-3 位全数字

3.2 isIpV6 — IPv6 校验(含 DNS 注入防御)

// ========== 合法 ==========
IpUtils.isIpV6("::1");                                      // true — 回环
IpUtils.isIpV6("2001:db8::1");                              // true — 压缩格式
IpUtils.isIpV6("fe80::1%eth0");                             // true — 带 Zone ID
IpUtils.isIpV6("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); // true — 完整格式

// ========== 非法(DNS 注入防御) ==========
IpUtils.isIpV6("localhost");    // false — 不触发 DNS 解析
IpUtils.isIpV6("google.com");  // false — 不触发 DNS 解析
IpUtils.isIpV6("192.168.1.1"); // false — 这是 IPv4

安全设计isIpV6 对不含 : 的字符串直接返回 false,不调用 InetAddress.getByName(),避免主机名触发 DNS 解析。

3.3 isIp — 联合判断

IpUtils.isIp("192.168.1.1");  // true — IPv4
IpUtils.isIp("::1");          // true — IPv6
IpUtils.isIp("not-an-ip");   // false

四、地址分类

4.1 私有/公网地址

// ========== 私有地址(RFC 1918) ==========
IpUtils.isPrivateIp("10.0.0.1");        // true — 10.x
IpUtils.isPrivateIp("172.16.0.1");      // true — 172.16-31.x
IpUtils.isPrivateIp("192.168.1.1");     // true — 192.168.x
IpUtils.isPrivateIp("8.8.8.8");        // false — 公网 IP

// ========== 公网可路由地址 ==========
IpUtils.isPublicIp("8.8.8.8");          // true — Google DNS
IpUtils.isPublicIp("1.1.1.1");          // true — Cloudflare DNS
IpUtils.isPublicIp("10.0.0.1");        // false — 私有
IpUtils.isPublicIp("127.0.0.1");       // false — 回环
IpUtils.isPublicIp("169.254.1.1");     // false — 链路本地
IpUtils.isPublicIp("0.0.0.0");         // false — "This"网络
IpUtils.isPublicIp("224.0.0.1");       // false — 组播
IpUtils.isPublicIp("255.255.255.255"); // false — 广播

4.2 特殊地址

// 回环地址
IpUtils.isLoopbackAddress("127.0.0.1");    // true
IpUtils.isLoopbackAddress("::1");          // true — IPv6 回环
IpUtils.isLoopbackAddress("localhost");    // false — DNS 注入防御

// 链路本地
IpUtils.isLinkLocal("169.254.1.1");       // true — IPv4 链路本地
IpUtils.isLinkLocal("fe80::1");           // true — IPv6 链路本地

// 组播地址
IpUtils.isMulticast("224.0.0.1");         // true
IpUtils.isMulticast("239.255.255.255");   // true

// 广播地址
IpUtils.isBroadcast("255.255.255.255");   // true

// IANA 保留(RFC 5737 测试网络 + RFC 6598 运营商级 NAT)
IpUtils.isReserved("192.0.2.1");          // true — TEST-NET-1
IpUtils.isReserved("100.64.0.1");         // true — 运营商级 NAT

// IP 地址分类(A/B/C/D/E)
IpUtils.getIpClass("10.0.0.1");          // 'A'
IpUtils.getIpClass("192.168.1.1");       // 'C'
IpUtils.getIpClass("224.0.0.1");         // 'D' — 组播

五、数值转换

5.1 IP ↔ long 互转

常用于 IP 地理位置数据库查询、IP 范围比较:

// IP → long
long ipLong = IpUtils.toLongByIp("192.168.1.1");   // 3232235777
IpUtils.toLongByIp("invalid");                      // -1 — 非法 IP

// long → IP
String ip = IpUtils.toIpByLong(3232235777L);        // "192.168.1.1"

5.2 IP ↔ byte[] 互转

常用于网络协议处理:

// IP → byte[4]
byte[] bytes = IpUtils.toBytes("192.168.1.1");
// bytes = {(byte)192, (byte)168, 1, 1}

// byte[4] → IP
String ip = IpUtils.fromBytes(new byte[]{(byte)192, (byte)168, 1, 1});
// "192.168.1.1"

5.3 子网掩码 ↔ CIDR 互转

// 掩码 → CIDR 前缀长度
IpUtils.maskToCidr("255.255.255.0");   // 24
IpUtils.maskToCidr("255.255.0.0");     // 16
IpUtils.maskToCidr("255.0.255.0");     // -1 — 非法掩码(非连续 1)

// CIDR 前缀长度 → 掩码
IpUtils.cidrToMask(24);                // "255.255.255.0"
IpUtils.cidrToMask(16);                // "255.255.0.0"
IpUtils.cidrToMask(33);                // null — 超范围

5.4 网络地址与广播地址

// 计算网络地址
IpUtils.getNetworkAddress("192.168.1.100", "255.255.255.0");  // "192.168.1.0"
IpUtils.getNetworkAddress("10.0.5.37", "255.255.0.0");        // "10.0.0.0"

// 计算广播地址
IpUtils.getBroadcastAddress("192.168.1.100", "255.255.255.0"); // "192.168.1.255"

// 子网可用主机数
IpUtils.getAvailableHostCount("255.255.255.0");    // 254(/24)
IpUtils.getAvailableHostCount("255.255.255.252");  // 2(/30)
IpUtils.getAvailableHostCount("255.255.255.254");  // 2(/31,点对点链路 RFC 3021)
IpUtils.getAvailableHostCount("255.255.255.255");  // 1(/32,单主机)

六、CIDR 子网匹配

6.1 isInSubnet — 单 CIDR 匹配

IpUtils.isInSubnet("192.168.1.5", "192.168.1.0/24");     // true
IpUtils.isInSubnet("192.168.2.5", "192.168.1.0/24");     // false
IpUtils.isInSubnet("10.0.0.1", "10.0.0.0/8");            // true
IpUtils.isInSubnet("10.0.0.1", "0.0.0.0/0");             // true — /0 匹配所有

// 边界情况
IpUtils.isInSubnet("192.168.1.100", "192.168.1.100/32"); // true — 精确匹配
IpUtils.isInSubnet("10.0.0.1", "10.0.0.0/33");           // false — 前缀超范围

6.2 isInSubnet — 多子网匹配(Collection)

// 从数据库/配置加载白名单子网
List<String> subnets = Arrays.asList("10.0.0.0/8", "192.168.1.0/24", "172.16.0.0/12");

IpUtils.isInSubnet("192.168.1.50", subnets);    // true — 匹配 192.168.1.0/24
IpUtils.isInSubnet("10.0.0.1", subnets);        // true — 匹配 10.0.0.0/8
IpUtils.isInSubnet("8.8.8.8", subnets);         // false — 不在任何子网中

// Set 用法
Set<String> allowedSubnets = new HashSet<>(Arrays.asList("10.0.0.0/8", "192.168.0.0/16"));
IpUtils.isInSubnet("10.0.0.1", allowedSubnets);  // true

6.3 isInRange — IP 范围检查

IpUtils.isInRange("192.168.1.50", "192.168.1.1", "192.168.1.100");   // true
IpUtils.isInRange("192.168.1.1", "192.168.1.1", "192.168.1.100");    // true — 含边界
IpUtils.isInRange("192.168.1.101", "192.168.1.1", "192.168.1.100");  // false

// start > end 时返回 false
IpUtils.isInRange("192.168.1.50", "192.168.1.100", "192.168.1.1");   // false

七、IP 段解析(四种格式统一处理)

IpUtils 的 IP 段方法统一支持四种格式:

格式 示例 说明
CIDR 192.168.1.0/24 标准 CIDR 表示法
范围 192.168.1.1-192.168.1.100 start-end
通配符 192.168.1.* * 代表 0-255
单 IP 192.168.1.1 精确匹配

7.1 matchIpRange — 自动识别格式

// CIDR
IpUtils.matchIpRange("192.168.1.5", "192.168.1.0/24");              // true

// 通配符
IpUtils.matchIpRange("192.168.1.50", "192.168.1.*");                // true

// 范围
IpUtils.matchIpRange("192.168.1.50", "192.168.1.1-192.168.1.100");  // true

// 单 IP
IpUtils.matchIpRange("192.168.1.1", "192.168.1.1");                 // true

7.2 countIpRange — IP 段内数量

IpUtils.countIpRange("192.168.1.0/24");              // 256
IpUtils.countIpRange("10.0.0.0/8");                  // 16777216
IpUtils.countIpRange("192.168.1.*");                 // 256
IpUtils.countIpRange("192.168.1.1-192.168.1.100");  // 100

7.3 listIpRange — 枚举 IP(上限 65536,防 OOM)

List<String> ips = IpUtils.listIpRange("192.168.1.0/30");
// ["192.168.1.0", "192.168.1.1", "192.168.1.2", "192.168.1.3"]

List<String> ips2 = IpUtils.listIpRange("10.0.0.1-10.0.0.5");
// ["10.0.0.1", "10.0.0.2", "10.0.0.3", "10.0.0.4", "10.0.0.5"]

// 超限返回空列表(防 OOM)
List<String> empty = IpUtils.listIpRange("10.0.0.0/15");  // 131072 > 65536
// []

八、批量匹配(白名单/黑名单)

8.1 matchAny / matchNone — 可变参数

// 白名单:IP 是否属于列表中任意一个
boolean inWhitelist = IpUtils.matchAny("192.168.1.5",
    "10.0.0.0/8",
    "192.168.1.0/24",
    "172.16.0.1"
);  // true — 匹配 192.168.1.0/24

// 混合格式支持
IpUtils.matchAny("192.168.1.50",
    "10.0.0.0/8",             // CIDR
    "192.168.1.*",            // 通配符
    "172.16.0.1-172.16.0.100" // 范围
);  // true — 匹配通配符

8.2 matchAny / matchNone — Collection(推荐)

// 从数据库/配置加载白名单
List<String> dbWhitelist = ipWhitelistRepository.findAllCidrs();
if (IpUtils.matchAny(clientIp, dbWhitelist)) {
    // 在白名单中,放行
}

// Set 用法(推荐,去重 + O(1) 查找)
Set<String> blacklist = new HashSet<>(Arrays.asList(
    "10.0.0.0/8", "192.168.0.0/16", "172.16.0.0/12"
));
boolean isBlocked = !IpUtils.matchNone("192.168.1.5", blacklist);  // true — 在黑名单中

九、实战:IP 白名单拦截器

@Component
public class IpWhitelistInterceptor implements HandlerInterceptor {

    // 从配置加载白名单
    @Value("${ip.whitelist:10.0.0.0/8,192.168.0.0/16}")
    private String whitelistConfig;

    private Set<String> whitelist;

    @PostConstruct
    public void init() {
        whitelist = new HashSet<>(Arrays.asList(whitelistConfig.split(",")));
    }

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        String clientIp = getClientIp(request);

        // 一行判断是否在白名单中
        if (!IpUtils.matchAny(clientIp, whitelist)) {
            response.setStatus(403);
            response.getWriter().write("{\"code\":403,\"msg\":\"IP不在白名单中\"}");
            return false;
        }
        return true;
    }

    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty()) {
            ip = request.getRemoteAddr();
        } else {
            ip = ip.split(",")[0].trim();
        }
        return ip;
    }
}

十、IDC 探测(SSRF 加固)

10.1 isPortOpen — 端口连通性检测

// 默认超时 5000ms
boolean open = IpUtils.isPortOpen("8.8.8.8", 53);

// 自定义超时
boolean open2 = IpUtils.isPortOpen("192.168.1.1", 80, 3000);

// SSRF 防御 — 主机名被拒绝
IpUtils.isPortOpen("localhost", 80);    // false — 非 IP 格式
IpUtils.isPortOpen("google.com", 443); // false — 非 IP 格式

安全设计isPortOpen 强制要求合法 IPv4 格式,拒绝主机名,防止攻击者通过传入主机名进行内网探测。

10.2 measureLatency — TCP 连接延迟

// 默认超时 5000ms
long latency = IpUtils.measureLatency("8.8.8.8", 53);  // 如 15(毫秒)

// 连接失败返回 -1
long failed = IpUtils.measureLatency("192.0.2.1", 80, 1000);  // -1

10.3 getReverseDns — PTR 反向 DNS

String hostname = IpUtils.getReverseDns("8.8.8.8");  // 如 "dns.google"
IpUtils.getReverseDns("192.168.1.1");                // null — 无 PTR 记录

// DNS 放大攻击防御
IpUtils.getReverseDns("localhost");   // null — 非 IP
IpUtils.getReverseDns("evil.com");   // null — 非 IP

10.4 实战:服务健康检查

@Service
public class ServiceHealthChecker {

    public List<ServiceStatus> checkAll(List<ServiceEndpoint> endpoints) {
        return endpoints.parallelStream().map(ep -> {
            boolean portOpen = IpUtils.isPortOpen(ep.getIp(), ep.getPort(), 3000);
            long latency = IpUtils.measureLatency(ep.getIp(), ep.getPort(), 3000);

            return ServiceStatus.builder()
                .name(ep.getName())
                .ip(ep.getIp())
                .port(ep.getPort())
                .alive(portOpen)
                .latencyMs(latency)
                .build();
        }).collect(Collectors.toList());
    }
}

十一、IP 脱敏

日志展示、接口返回中的 IP 需要脱敏处理:

// 可配置保留段数
IpUtils.hideIp("192.168.1.100", 3);   // "192.168.1.***"
IpUtils.hideIp("192.168.1.100", 2);   // "192.168.***.***"
IpUtils.hideIp("192.168.1.100", 1);   // "192.***.***.***"

// 快捷脱敏(保留前 3 段)
IpUtils.hideLocalIp();                // 如 "192.168.1.***"
IpUtils.hideOutIp("10.0.0.5");       // "10.0.0.***"

实战:日志脱敏

// 在日志输出前脱敏
logger.info("用户 {} 从 IP {} 登录", username, IpUtils.hideOutIp(clientIp));
// 输出:用户 admin 从 IP 192.168.1.*** 登录

十二、本机网络信息

// 获取主机名
String hostname = IpUtils.getLocalHostName();   // 如 "myhost"

// 获取本机 IP
String ip = IpUtils.getLocalAddress();          // 如 "192.168.1.100"

// 获取所有 IPv4 地址(多网卡场景)
List<String> allIps = IpUtils.getIpV4AllAddress();
// ["127.0.0.1", "192.168.1.100", "10.0.0.5"]

// 获取有效业务 IP(过滤回环和链路本地)
List<String> effectiveIps = IpUtils.getEffectiveAddresses();
// ["192.168.1.100", "10.0.0.5"] — 不含 127.0.0.1

// 按前缀匹配本机 IP(多网卡场景)
String dockerIp = IpUtils.prefixIpv4LocalAddress("172.17");   // Docker 网卡
String lanIp = IpUtils.prefixIpv4LocalAddress("192.168");     // 局域网网卡

// 获取 MAC 地址
String mac = IpUtils.getMacAddress("192.168.1.100");  // 如 "AA:BB:CC:DD:EE:FF"

// 获取机器码(基于网卡哈希,用于软件授权)
int machineCode = IpUtils.getMachine();

十三、安全特性汇总

IpUtils 内置 13 项安全加固,全部默认开启:

安全特性 防御目标 涉及方法
DNS 主机名注入防御 防止 localhost/evil.com 触发 DNS 解析 isIpV6, isLoopbackAddress, isLinkLocal, isSiteLocal, isIpV6Loopback
SSRF 防御 防止内网探测(如 169.254.169.254 云元数据) isPortOpen, measureLatency
DNS 放大攻击防御 防止主机名注入导致 DNS 放大 getReverseDns
整数溢出保护 CIDR 掩码计算使用 long 算术 maskToCidr, getNetworkAddress, getBroadcastAddress
前导零拒绝 防止八进制解析歧义 parseSegments(所有 IP 校验的内核)
OOM 防护 listIpRange 上限 65536 listIpRange
Socket 资源管理 try-with-resources 确保关闭 isPortOpen, measureLatency

十四、API 速查表

格式校验

方法 说明
isIpV4(String) 严格 IPv4 校验(含前导零拒绝)
isIpV6(String) IPv6 校验(含 DNS 注入防御)
isIp(String) IPv4 或 IPv6 联合判断

地址分类

方法 说明
isPrivateIp(String) RFC 1918 私有地址
isPublicIp(String) / isRoutable(String) 公网可路由地址
isLoopbackAddress(String) 回环地址(IPv4 + IPv6)
isLinkLocal(String) 链路本地
isMulticast(String) 组播地址
isBroadcast(String) 广播地址
isReserved(String) IANA 保留地址
getIpClass(String) A/B/C/D/E 分类

数值转换

方法 说明
toLongByIp(String) / toIpByLong(long) IP ↔ long
toBytes(String) / fromBytes(byte[]) IP ↔ byte[4]
maskToCidr(String) / cidrToMask(int) 掩码 ↔ CIDR
getNetworkAddress(String, String) 计算网络地址
getBroadcastAddress(String, String) 计算广播地址
getAvailableHostCount(String) 子网可用主机数

CIDR 与 IP 段

方法 说明
isInSubnet(String, String) 单 CIDR 匹配
isInSubnet(String, Collection<String>) 多 CIDR 匹配
isInRange(String, String, String) IP 范围检查
matchIpRange(String, String) 自动识别格式匹配
countIpRange(String) IP 段内数量
listIpRange(String) 枚举 IP(上限 65536)
matchAny(String, String...) 白名单匹配
matchNone(String, String...) 黑名单匹配

IDC 探测

方法 说明
isPortOpen(String, int) TCP 端口连通性(SSRF 加固)
measureLatency(String, int) TCP 连接延迟(SSRF 加固)
getReverseDns(String) PTR 反向 DNS(DNS 放大防御)
getMacAddress(String) MAC 地址查询
getEffectiveAddresses() 有效业务 IP

脱敏与本机信息

方法 说明
hideIp(String, int) 可配置保留段数脱敏
hideLocalIp() / hideOutIp(String) 快捷脱敏
getLocalAddress() 本机 IP
getIpV4AllAddress() 所有 IPv4 地址
prefixIpv4LocalAddress(String) 按前缀匹配本机 IP
getMachine() 机器码

十五、注意事项

问题 说明
isIpV6 不接受主机名 不含 : 的字符串直接返回 false,不触发 DNS
isPortOpen 不接受主机名 SSRF 防御,强制合法 IPv4 格式
listIpRange 上限 65536 防 OOM,更大范围用 isInSubnet 按需匹配
toLongByIp 返回 -1 非法 IP 返回哨兵值,不抛异常
getAvailableHostCount /31 返回 2 RFC 3021 点对点链路,/31 两个 IP 均可用
零外部依赖 仅使用 JDK 标准库(java.net, java.io)

十六、总结

维度 手写 IP 工具 IpUtils
校验严格性 正则校验,前导零通过 内置 13 项安全加固
IPv6 支持 通常只支持 IPv4 完整 IPv6(含 Zone ID)
CIDR 计算 每次翻文档手写 一行调用
白名单/黑名单 遍历 List 逐个比 matchAny/matchNone 批量匹配
安全性 无 SSRF 防御 内置 SSRF/DNS 注入防御
依赖 可能依赖 Guava 等 零外部依赖

源码地址pan-common


如果这篇文章对你有帮助,欢迎点赞、收藏、关注,后续将持续更新 Java 工具类实战系列。

更多推荐