如何在 Java 中的 HTTP 连接中覆盖 DNS

2022-09-04 19:37:27

Curl 具有手动指定要将主机解析到的 IP 的功能。例如:

curl https://google.com --resolve "google.com:443:173.194.72.113"

这在使用 HTTPS 时特别有用。如果它只是一个HTTP请求,我可以通过直接指定IP地址并添加主机标头来实现相同的目标。但是在HTTPS中,这将中断连接,因为SSL证书主机将与IP地址进行比较,而不是主机标头。

我的问题是,我如何在Java中实现同样的事情?


答案 1

如果使用Apache的HttpClient,您可以创建自定义DNS解析程序来检测要重定向的主机,然后提供替代IP地址。

注意:仅更改 HTTPS 请求的主机标头不起作用。它会抛出“javax.net.ssl.SSLPeerUnverifiedException”,迫使你信任坏证书,阻止SNI工作等,所以真的不是一个选择。自定义DnsResolver是我发现的让这些请求在Java中使用HTTPS的唯一干净方法。

例:

/* Custom DNS resolver */
DnsResolver dnsResolver = new SystemDefaultDnsResolver() {
    @Override
    public InetAddress[] resolve(final String host) throws UnknownHostException {
        if (host.equalsIgnoreCase("my.host.com")) {
            /* If we match the host we're trying to talk to, 
               return the IP address we want, not what is in DNS */
            return new InetAddress[] { InetAddress.getByName("127.0.0.1") };
        } else {
            /* Else, resolve it as we would normally */
            return super.resolve(host);
        }
    }
};

/* HttpClientConnectionManager allows us to use custom DnsResolver */
BasicHttpClientConnectionManager connManager = new BasicHttpClientConnectionManager(
    /* We're forced to create a SocketFactory Registry.  Passing null
       doesn't force a default Registry, so we re-invent the wheel. */
    RegistryBuilder.<ConnectionSocketFactory>create()
        .register("http", PlainConnectionSocketFactory.getSocketFactory())
        .register("https", SSLConnectionSocketFactory.getSocketFactory())
        .build(), 
    null, /* Default ConnectionFactory */ 
    null, /* Default SchemePortResolver */ 
    dnsResolver  /* Our DnsResolver */
    );

/* build HttpClient that will use our DnsResolver */
HttpClient httpClient = HttpClientBuilder.create()
        .setConnectionManager(connManager)
        .build();

/* build our request */
HttpGet httpRequest = new HttpGet("https://my.host.com/page?and=stuff"); 

/* Executing our request should now hit 127.0.0.1, regardless of DNS */
HttpResponse httpResponse = httpClient.execute(httpRequest);

答案 2

我没有代码近在咫尺,但你也可以编写自己的SSL处理程序/检查器,它可以适应或完全忽略所有的安全性。使用JDK基础网络,我们不得不在内部完全忽略SSL证书进行测试。应该很容易找到例子。