问题描述
我正在努力让我的 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
}
这主要基于 documentation from the OkHttp GitHub site,它描述了如何将值传递给 Builder.socketFactory()
允许我们通过覆盖无参数的 createSocket
方法将每个套接字绑定到特定地址。但是,docs for sslSocketFactory 没有提到任何关于能够绑定套接字的内容。当我使用上述代码运行我的应用程序时,永远不会生成 createSocket
日志条目,表明完全忽略了工厂。因此,永远无法到达 HTTP 端点。
如果我在没有包装 MySocketFactory
类的情况下尝试相同的设置 - 而只是将 getSslContext().socketFactory
直接传递到 Builder.sslSocketFactory
- 那么我可以很好地联系端点,假设我只有一个当时我机器上的本地地址。
所以我的问题是:这可以通过自定义 SSLSocketFactory
来实现吗?
解决方法
是的!但不是自定义 SSLSocketFactory,而是自定义常规 SocketFactory。当 OkHttp 创建任何套接字时,它总是首先使用常规的 SocketFactory,然后可能用 SSL 包装它。这对于 TLS 隧道是必需的,但在任何地方都可以使用。
,使用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,0)
}
override fun createSocket(
host: String,port: Int,localAddr: InetAddress,localPort: Int
): Socket {
return systemFactory.createSocket(host,localAddr,localPort)
}
override fun createSocket(
address: InetAddress,localPort: Int
): Socket {
return systemFactory.createSocket(address,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)
}
}
}