开发者社区 问答 正文

使用HTTPDNS如何解决HTTPS的问题?

Android使用HTTPDNS,如果使用的是Apache HttpClient或者okHttp时如何解决HTTPS的问题?
https://help.aliyun.com/document_detail/httpdns/practice/https.html?spm=5176.dochttpdns/api-reference/android.2.3.V8k0NX 示例中只给出了HttpURLConnection的方案。

展开
收起
下班我不走 2016-04-12 11:01:31 3726 分享 版权
阿里云 AI 助理回答

在Android端使用HTTPDNS时,如果网络框架是 Apache HttpClientOkHttp,解决HTTPS问题需要针对SSL/TLS握手过程中可能出现的域名验证失败进行特殊处理。以下是详细的解决方案:


1. OkHttp 场景下的 HTTPS 解决方案

背景

当使用HTTPDNS解析域名时,请求URL中的host会被替换为解析出的IP地址。这会导致SSL/TLS握手过程中,客户端对服务端证书的域名校验失败(因为证书中通常不包含IP地址)。此外,在SNI(Server Name Indication)场景下,服务器可能无法根据IP地址返回正确的证书。

解决方案

通过自定义 Dns 接口和 SSLSocketFactory,可以优雅地解决上述问题。

步骤 1:实现自定义 DNS 接口

通过实现 OkHttpDns 接口,将HTTPDNS解析出的IP地址注入到网络请求中:

class OkHttpDns constructor(private val context: Context) : Dns {
    override fun lookup(hostname: String): List<InetAddress> {
        val httpdnsResult = HttpDns.getService(context, accountID)
            .getHttpDnsResultForHostSync(hostname, RequestIpType.both)
        val inetAddresses = mutableListOf<InetAddress>()
        try {
            httpdnsResult.ips?.forEach { ipv4 ->
                inetAddresses.add(InetAddress.getByName(ipv4))
            }
            httpdnsResult.ipv6s?.forEach { ipv6 ->
                inetAddresses.add(InetAddress.getByName(ipv6))
            }
        } catch (e: UnknownHostException) {
            // 处理异常
        }
        return if (inetAddresses.isNotEmpty()) inetAddresses else Dns.SYSTEM.lookup(hostname)
    }
}
步骤 2:配置 OkHttpClient

将自定义的 OkHttpDns 配置到 OkHttpClient 中:

val client = OkHttpClient.Builder()
    .dns(OkHttpDns(context))
    .build()
步骤 3:处理 SNI 和 Hostname 验证

通过自定义 SSLSocketFactoryHostnameVerifier,解决SNI和域名验证问题:

class TlsSniSocketFactory(private val hostname: String) : SSLSocketFactory() {
    private val delegate = getDefault()

    override fun createSocket(socket: Socket?, host: String?, port: Int, autoClose: Boolean): Socket {
        val sslSocket = delegate.createSocket(socket, host, port, autoClose) as SSLSocket
        sslSocket.enabledProtocols = arrayOf("TLSv1.2") // 设置支持的协议
        return sslSocket
    }

    override fun getDefaultCipherSuites(): Array<String> = delegate.defaultCipherSuites

    override fun getSupportedCipherSuites(): Array<String> = delegate.supportedCipherSuites
}

val client = OkHttpClient.Builder()
    .sslSocketFactory(TlsSniSocketFactory("example.com"), trustManager)
    .hostnameVerifier { _, _ -> true } // 忽略主机名验证
    .build()

2. Apache HttpClient 场景下的 HTTPS 解决方案

背景

OkHttp 类似,Apache HttpClient 在使用HTTPDNS时也会遇到域名验证和SNI问题。由于 Apache HttpClient 不直接提供类似 OkHttp 的自定义接口,因此需要通过手动设置 HostnameVerifierSSLSocketFactory 来解决问题。

解决方案

步骤 1:自定义 HostnameVerifier

忽略主机名验证,避免因IP地址导致的域名不匹配问题:

HostnameVerifier hostnameVerifier = (hostname, session) -> true; // 忽略主机名验证
步骤 2:自定义 SSLSocketFactory

创建一个自定义的 SSLSocketFactory,用于设置SNI信息:

public class TlsSniSocketFactory extends SSLSocketFactory {
    private final String hostname;

    public TlsSniSocketFactory(String hostname) {
        this.hostname = hostname;
    }

    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
        SSLSocket socket = (SSLSocket) super.createSocket(s, host, port, autoClose);
        SSLParameters params = new SSLParameters();
        params.setServerNames(Collections.singletonList(new SNIHostName(hostname)));
        socket.setSSLParameters(params);
        return socket;
    }

    // 其他方法省略...
}
步骤 3:配置 HttpClient

将自定义的 SSLSocketFactoryHostnameVerifier 配置到 HttpClient 中:

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, null, null);

TlsSniSocketFactory tlsSniSocketFactory = new TlsSniSocketFactory("example.com");

CloseableHttpClient httpClient = HttpClients.custom()
    .setSSLContext(sslContext)
    .setSSLHostnameVerifier((hostname, session) -> true)
    .setSSLSocketFactory(tlsSniSocketFactory)
    .build();

3. 通用注意事项

  • Cookie 管理:部分网络库会将URL中的IP地址作为Cookie的域名存储,可能导致Cookie管理混乱。建议关闭自动Cookie管理功能。
  • 代理环境:在存在中间代理的情况下,建议关闭HTTPDNS功能,以避免代理网关误用IP地址作为真实HOST信息传递给目标服务器。
  • WebView 场景:如果需要在WebView中使用HTTPDNS,请参考相关最佳实践文档。

总结

通过上述方法,可以在 OkHttpApache HttpClient 中有效解决HTTPS场景下的域名验证和SNI问题。关键在于自定义 DnsSSLSocketFactoryHostnameVerifier,并确保正确设置SNI信息和忽略不必要的域名验证。

有帮助
无帮助
AI 助理回答生成答案可能存在不准确,仅供参考
0 条回答
写回答
取消 提交回答
问答分类:
问答标签:
问答地址: