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的方案。
版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。
在Android端使用HTTPDNS时,如果网络框架是 Apache HttpClient 或 OkHttp,解决HTTPS问题需要针对SSL/TLS握手过程中可能出现的域名验证失败进行特殊处理。以下是详细的解决方案:
当使用HTTPDNS解析域名时,请求URL中的host
会被替换为解析出的IP地址。这会导致SSL/TLS握手过程中,客户端对服务端证书的域名校验失败(因为证书中通常不包含IP地址)。此外,在SNI(Server Name Indication)场景下,服务器可能无法根据IP地址返回正确的证书。
通过自定义 Dns
接口和 SSLSocketFactory
,可以优雅地解决上述问题。
通过实现 OkHttp
的 Dns
接口,将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)
}
}
将自定义的 OkHttpDns
配置到 OkHttpClient
中:
val client = OkHttpClient.Builder()
.dns(OkHttpDns(context))
.build()
通过自定义 SSLSocketFactory
和 HostnameVerifier
,解决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()
与 OkHttp
类似,Apache HttpClient
在使用HTTPDNS时也会遇到域名验证和SNI问题。由于 Apache HttpClient
不直接提供类似 OkHttp
的自定义接口,因此需要通过手动设置 HostnameVerifier
和 SSLSocketFactory
来解决问题。
忽略主机名验证,避免因IP地址导致的域名不匹配问题:
HostnameVerifier hostnameVerifier = (hostname, session) -> true; // 忽略主机名验证
创建一个自定义的 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;
}
// 其他方法省略...
}
将自定义的 SSLSocketFactory
和 HostnameVerifier
配置到 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();
通过上述方法,可以在 OkHttp 和 Apache HttpClient 中有效解决HTTPS场景下的域名验证和SNI问题。关键在于自定义 Dns
、SSLSocketFactory
和 HostnameVerifier
,并确保正确设置SNI信息和忽略不必要的域名验证。