如何在Java代码中解析服务FQDN至IP地址(无/etc/hosts权限)
解决方案
你的核心矛盾是:既需要让Java将abc.xyz.com解析到指定IP,又要保证SSL握手时使用正确的FQDN(避免证书验证失败)。以下是几种可行的代码层面解决方案:
方法1:针对HttpsURLConnection的局部处理(最稳妥,无全局影响)
直接连接到目标IP,但强制SSL验证时使用原FQDN,同时设置请求头的Host字段匹配服务端预期:
import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import java.io.IOException; import java.io.InputStream; import java.net.URL; public class HttpsConnectionFix { public static void main(String[] args) throws IOException { String targetFqdn = "abc.xyz.com"; String targetIp = "100.70.125.57"; // 构造指向IP的HTTPS URL URL url = new URL("https://" + targetIp); HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); // 覆盖主机名验证逻辑,验证原FQDN而非IP conn.setHostnameVerifier((hostname, session) -> { // 优先验证原FQDN,也可复用默认验证逻辑 return targetFqdn.equals(hostname) || HostnameVerifier.getDefault().verify(targetFqdn, session); }); // 设置请求头Host字段,确保服务端识别请求目标 conn.setRequestProperty("Host", targetFqdn); // 后续正常处理连接流 try (InputStream in = conn.getInputStream()) { // 读取响应内容 } } }
方法2:手动构建SSL Socket连接
如果需要更底层的控制,可以直接创建Socket连接到IP,再包装为SSL Socket并指定验证的主机名:
import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; public class SslSocketFix { public static void main(String[] args) throws IOException { String targetFqdn = "abc.xyz.com"; String targetIp = "100.70.125.57"; int port = 443; // 建立到目标IP的基础Socket try (Socket plainSocket = new Socket(targetIp, port)) { // 创建SSL Socket,指定握手时验证的主机名为原FQDN SSLSocketFactory sslFactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); try (SSLSocket sslSocket = (SSLSocket) sslFactory.createSocket( plainSocket, targetFqdn, port, true // 自动关闭底层Socket )) { // 启动SSL握手 sslSocket.startHandshake(); // 后续通过sslSocket进行读写操作 try (OutputStream out = sslSocket.getOutputStream(); InputStream in = sslSocket.getInputStream()) { // 发送请求、读取响应 } } } } }
方法3:使用OkHttp客户端的自定义DNS解析(适合用OkHttp的场景)
如果项目使用OkHttp作为HTTP客户端,可以直接自定义DNS规则,既实现FQDN到IP的映射,又自动处理SSL主机名验证:
import okhttp3.Dns; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Collections; public class OkHttpDnsFix { public static void main(String[] args) throws Exception { String targetFqdn = "abc.xyz.com"; String targetIp = "100.70.125.57"; OkHttpClient client = new OkHttpClient.Builder() .dns(hostname -> { if (targetFqdn.equals(hostname)) { // 强制解析到指定IP return Collections.singletonList(InetAddress.getByAddress(hostname, new byte[]{100, 70, 125, 57})); } // 其他域名使用系统默认DNS解析 return Dns.SYSTEM.lookup(hostname); }) .build(); // 正常请求原FQDN即可,OkHttp会用自定义DNS解析到目标IP Request request = new Request.Builder() .url("https://" + targetFqdn) .build(); try (Response response = client.newCall(request).execute()) { // 处理响应 } } }
方法4:全局DNS解析替换(Java 9+,影响整个JVM)
如果需要所有JVM内的连接都将abc.xyz.com解析到指定IP,可以使用Java 9引入的InetAddressResolverProvider实现全局自定义DNS:
import java.net.InetAddress; import java.net.InetAddressResolver; import java.net.InetAddressResolverProvider; import java.net.UnknownHostException; import java.util.Collections; import java.util.concurrent.CompletionStage; public class CustomDnsProvider extends InetAddressResolverProvider { @Override public InetAddressResolver get(Configuration configuration) { return new InetAddressResolver() { // 复用默认解析器处理其他域名 private final InetAddressResolver defaultResolver = InetAddressResolverProvider.provider().get(configuration); @Override public CompletionStage<java.util.List<InetAddress>> lookupByName(String host, LookupPolicy lookupPolicy) { if ("abc.xyz.com".equals(host)) { try { InetAddress customAddr = InetAddress.getByAddress(host, new byte[]{100, 70, 125, 57}); return java.util.concurrent.CompletableFuture.completedFuture(Collections.singletonList(customAddr)); } catch (UnknownHostException e) { return java.util.concurrent.CompletableFuture.failedFuture(e); } } return defaultResolver.lookupByName(host, lookupPolicy); } @Override public CompletionStage<String> lookupByAddress(byte[] addr) { return defaultResolver.lookupByAddress(addr); } }; } @Override public String name() { return "custom-fqdn-resolver"; } }
启动JVM时添加参数,注册自定义解析器:
java -Djava.net.spi.nameservice.provider.1=custom,com.yourpackage.CustomDnsProvider YourMainClass
注意:此方法会全局生效,影响所有域名解析,仅在必要时使用。
内容的提问来源于stack exchange,提问作者Om Sao
相关产品推荐
相关产品推荐

