我正在努力让我的OkHttpClient在Android上使用自定义证书发出HTTPS请求,同时绑定到特定网络接口的本地地址。我目前尝试使用以下OkHttpClient
:
val client = OkHttpClient.Builder()
.readTimeout(2, TimeUnit.SECONDS)
.connectTimeout(2, TimeUnit.SECONDS)
.sslSocketFactory(
MySocketFactory(
getSslContext().socketFactory,
localAddress
),
MyTrustManager()
)
.build()
MySocketFactory
类实现为:
class MySocketFactory(
private val delegate: SSLSocketFactory,
private val localAddress: InetAddress
) : SSLSocketFactory() {
override fun createSocket(): Socket {
Timber.i("createSocket has been called!")
val socket = delegate.createSocket()
socket.bind(InetSocketAddress(localAddress, 0))
return socket
}
[other overrides of the various abstract createSocket methods, each of which throws an UnsupportedOperationException]
override fun getDefaultCipherSuites() = delegate.defaultCipherSuites
override fun getSupportedCipherSuites() = delegate.supportedCipherSuites
}
这主要基于OkHttp GitHub网站上的文档,该文档描述了如何将值传递给Builder.socketFactory()
,使我们能够通过重写无参数的createSocket
方法将每个套接字绑定到特定地址。然而,sslSocketFactory的文档中没有提到任何关于绑定套接字的内容。当我用上面的代码运行我的应用程序时,createSocket
日志条目从未生成,这表明工厂被完全忽略。因此,永远无法到达HTTP端点。
如果我在没有包装器MySocketFactory
类的情况下尝试同样的设置,而只是将getSslContext().socketFactory
直接传递到Builder.sslSocketFactory
中,那么我可以很好地联系端点,假设我当时在机器上只有一个本地地址。
所以我的问题是:这可能与自定义SSLSocketFactory
有关吗?
是!但是,与其自定义SSLSocketFactory,不如自定义常规SocketFactory。当OkHttp创建任何套接字时,它总是首先使用常规的SocketFactory,然后可能用SSL封装它。这对于TLS隧道是必要的,但在任何地方都可以使用。
https://github.com/yschimke/okurl/blob/0abaa8510dd5466d5e9a08ebe33a009c491749bf/src/main/kotlin/com/baulsupp/okurl/network/InterfaceSocketFactory.kt
使用builder.socketFactory(getSocketFactory())
。此示例代码应根据IP或名称进行选择。
class InterfaceSocketFactory(private val localAddress: InetAddress) : SocketFactory() {
private val systemFactory = getDefault()
override fun createSocket(): Socket {
val s = systemFactory.createSocket()
s.bind(InetSocketAddress(localAddress, 0))
return s
}
override fun createSocket(host: String, port: Int): Socket {
return systemFactory.createSocket(host, port, localAddress, 0)
}
override fun createSocket(address: InetAddress, port: Int): Socket {
return systemFactory.createSocket(address, port, localAddress, 0)
}
override fun createSocket(
host: String,
port: Int,
localAddr: InetAddress,
localPort: Int
): Socket {
return systemFactory.createSocket(host, port, localAddr, localPort)
}
override fun createSocket(
address: InetAddress,
port: Int,
localAddr: InetAddress,
localPort: Int
): Socket {
return systemFactory.createSocket(address, port, localAddr, localPort)
}
companion object {
fun byName(ipOrInterface: String): SocketFactory? {
val localAddress = try {
// example 192.168.0.51
InetAddress.getByName(ipOrInterface)
} catch (uhe: UnknownHostException) {
// example en0
val networkInterface = NetworkInterface.getByName(ipOrInterface) ?: return null
networkInterface.inetAddresses.nextElement()
}
return InterfaceSocketFactory(localAddress)
}
}
}