RFC 1918 私有地址段定義
| CIDR | 範圍 | 掩碼長度 | 適用場景 |
| 10.0.0.0/8 | 10.0.0.0 – 10.255.255.255 | 8 | A 類大型內網 |
| 172.16.0.0/12 | 172.16.0.0 – 172.31.255.255 | 12 | B 類中型內網 |
| 192.168.0.0/16 | 192.168.0.0 – 192.168.255.255 | 16 | C 類小型內網/家用路由 |
此外,回環地址 127.0.0.0/8、鏈路本地 169.254.0.0/16 也屬非公網。
核心算法:IPv4 轉長整型 + 位運算判斷
public final class IpUtils {
private IpUtils() {}
/** 判斷 IPv4 是否為私有/內網地址 */
public static boolean isPrivateIpv4(String ip) {
long ipNum = ipv4ToLong(ip);
return isPrivateIpv4(ipNum);
}
public static boolean isPrivateIpv4(long ipNum) {
// 10.0.0.0/8 -> 0x0A000000 - 0x0AFFFFFF
// 172.16.0.0/12 -> 0xAC100000 - 0xAC1FFFFF
// 192.168.0.0/16 -> 0xC0A80000 - 0xC0A8FFFF
// 127.0.0.0/8 -> 0x7F000000 - 0x7FFFFFFF
// 169.254.0.0/16 -> 0xA9FE0000 - 0xA9FEFFFF
return (ipNum >= 0x0A000000L && ipNum <= 0x0AFFFFFFL) || // 10/8
(ipNum >= 0xAC100000L && ipNum <= 0xAC1FFFFFL) || // 172.16/12
(ipNum >= 0xC0A80000L && ipNum <= 0xC0A8FFFFL) || // 192.168/16
(ipNum >= 0x7F000000L && ipNum <= 0x7FFFFFFFL) || // 127/8
(ipNum >= 0xA9FE0000L && ipNum <= 0xA9FEFFFFL); // 169.254/16
}
/** IPv4 字符串 -> 無符號長整型 (大端序) */
public static long ipv4ToLong(String ip) {
String[] octets = ip.split("\\.");
if (octets.length != 4) throw new IllegalArgumentException("Invalid IPv4: " + ip);
long num = 0;
for (String octet : octets) {
int val = Integer.parseInt(octet);
if (val < 0 || val > 255) throw new IllegalArgumentException("Octet out of range: " + val);
num = (num << 8) | val;
}
return num;
}
/** 長整型 -> IPv4 字符串 */
public static String longToIpv4(long num) {
return ((num >>> 24) & 0xFF) + "." +
((num >>> 16) & 0xFF) + "." +
((num >>> 8) & 0xFF) + "." +
(num & 0xFF);
}
}
IPv6 內網判斷(簡略版)
public static boolean isPrivateIpv6(String ip) {
// 簡化:唯一本地地址 ULA (fc00::/7) + 鏈路本地 fe80::/10 + 回環 ::1/128
// 完整實現建議用 InetAddress.getByName(ip).isSiteLocalAddress() / isLinkLocalAddress()
try {
InetAddress addr = InetAddress.getByName(ip);
return addr.isSiteLocalAddress() || addr.isLinkLocalAddress() || addr.isLoopbackAddress();
} catch (UnknownHostException e) {
return false;
}
}
Spring Boot 整合:Request 過濾器示例
@Component
public class IpAccessFilter extends OncePerRequestFilter {
private static final Set ALLOWED_PRIVATE = Set.of("10.0.0.0/8", "192.168.0.0/16"); // 配置化
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String clientIp = getClientIp(request); // 需處理 X-Forwarded-For, X-Real-IP
if (IpUtils.isPrivateIpv4(clientIp) && !isWhitelisted(clientIp)) {
response.setStatus(HttpStatus.FORBIDDEN.value());
response.getWriter().write("內網 IP 訪問被拒絕");
return;
}
chain.doFilter(request, response);
}
// ... getClientIp, isWhitelisted 實現略
}
性能對比:字符串匹配 vs 位運算
| 方法 | 耗時 (ns/op) | 備註 |
| 正則/字符串 startsWith | ~150 ns | 分配對象、字符比較 |
| InetAddress + isSiteLocalAddress | ~300 ns | JNI 開銷、緩存查找 |
| 位運算 (本文方案) | ~8 ns | 純算術、零分配、JIT 內聯極易 |
總結
- IPv4 內網判斷首選位運算,納秒級、零 GC、無依賴;
- 注意處理
X-Forwarded-For 等代理頭,獲取真實客戶端 IP;
- IPv6 建議直接用
InetAddress.isSiteLocalAddress(),避免手寫解析錯誤;
- 單元測試務必覆蓋邊界:10.0.0.0、10.255.255.255、172.16.0.0、172.31.255.255、192.168.0.0、192.168.255.255。