You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何结合Retrofit与Hilt动态提供CA证书/SSLContext?

无需重建Retrofit/OkHttpClient实现动态证书切换的方案

你的核心诉求是避免重建Retrofit实例,同时支持动态切换基站的证书(SSLContext),结合你已经实现的动态baseUrl拦截器,可以通过动态代理OkHttp的SSLSocketFactory来实现,核心思路是让OkHttp在每次请求时使用最新的SSLContext,而不是依赖不可变的OkHttpClient实例。

具体实现步骤

1. 定义动态SSLContext持有者

创建一个线程安全的单例类,用于存储当前基站的SSLContext和对应的TrustManager,方便后续动态更新:

object DynamicSSLContextHolder {
    @Volatile
    var currentSSLContext: SSLContext? = null
    @Volatile
    var currentTrustManager: X509TrustManager? = null

    fun updateSSLContext(sslContext: SSLContext, trustManager: X509TrustManager) {
        currentSSLContext = sslContext
        currentTrustManager = trustManager
    }
}

2. 实现动态SSLSocketFactory

自定义SSLSocketFactory,每次创建Socket时读取DynamicSSLContextHolder中的最新SSLContext,替代OkHttp默认的静态SSLSocketFactory:

class DynamicSSLSocketFactory : SSLSocketFactory() {
    private val defaultFactory = SSLContext.getDefault().socketFactory

    private val currentFactory: SSLSocketFactory
        get() = DynamicSSLContextHolder.currentSSLContext?.socketFactory ?: defaultFactory

    override fun createSocket(): Socket = currentFactory.createSocket()
    override fun createSocket(s: Socket?, host: String?, port: Int, autoClose: Boolean): Socket =
        currentFactory.createSocket(s, host, port, autoClose)
    override fun createSocket(host: String?, port: Int): Socket = currentFactory.createSocket(host, port)
    override fun createSocket(host: String?, port: Int, localHost: InetAddress?, localPort: Int): Socket =
        currentFactory.createSocket(host, port, localHost, localPort)
    override fun createSocket(host: InetAddress?, port: Int): Socket = currentFactory.createSocket(host, port)
    override fun createSocket(address: InetAddress?, port: Int, localAddress: InetAddress?, localPort: Int): Socket =
        currentFactory.createSocket(address, port, localAddress, localPort)
    override fun getDefaultCipherSuites(): Array<String> = currentFactory.defaultCipherSuites
    override fun getSupportedCipherSuites(): Array<String> = currentFactory.supportedCipherSuites
}

3. 实现动态TrustManager

同样需要一个动态的TrustManager,代理到当前基站的TrustManager,确保证书验证逻辑随基站切换更新:

class DynamicTrustManager : X509TrustManager {
    override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {
        DynamicSSLContextHolder.currentTrustManager?.checkClientTrusted(chain, authType)
    }

    override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
        DynamicSSLContextHolder.currentTrustManager?.checkServerTrusted(chain, authType)
    }

    override fun getAcceptedIssuers(): Array<X509Certificate> {
        return DynamicSSLContextHolder.currentTrustManager?.acceptedIssuers ?: emptyArray()
    }
}

4. Hilt中配置OkHttpClient和Retrofit

在Hilt模块中注入上述动态组件,确保OkHttpClient和Retrofit只初始化一次:

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Provides
    @Singleton
    fun provideDynamicSSLSocketFactory(): SSLSocketFactory = DynamicSSLSocketFactory()

    @Provides
    @Singleton
    fun provideDynamicTrustManager(): X509TrustManager = DynamicTrustManager()

    @Provides
    @Singleton
    fun provideBaseUrlInterceptor(): BaseUrlInterceptor = BaseUrlInterceptor()

    @Provides
    @Singleton
    fun provideOkHttpClient(
        sslSocketFactory: SSLSocketFactory,
        trustManager: X509TrustManager,
        baseUrlInterceptor: BaseUrlInterceptor
    ): OkHttpClient {
        return OkHttpClient.Builder()
            .sslSocketFactory(sslSocketFactory, trustManager)
            .addInterceptor(baseUrlInterceptor)
            // 其他通用配置:超时、日志拦截器等
            .build()
    }

    @Provides
    @Singleton
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://default-base-url.com") // 默认值,实际由拦截器替换
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
}

5. 切换基站时更新配置

当用户选择新基站后,生成对应基站的SSLContext和TrustManager,调用DynamicSSLContextHolder.updateSSLContext(),同时更新baseUrl拦截器的当前baseUrl:

// 示例:从基站证书文件生成SSLContext
fun switchBaseStation(newBaseUrl: String, certificateInputStream: InputStream) {
    // 1. 更新baseUrl
    val baseUrlInterceptor = // 通过Hilt注入获取实例
    baseUrlInterceptor.currentBaseUrl = newBaseUrl

    // 2. 生成并更新SSLContext
    val certificateFactory = CertificateFactory.getInstance("X.509")
    val certificate = certificateFactory.generateCertificate(certificateInputStream)

    val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
    keyStore.load(null, null)
    keyStore.setCertificateEntry("base-station-cert", certificate)

    val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
    trustManagerFactory.init(keyStore)
    val trustManager = trustManagerFactory.trustManagers.first() as X509TrustManager

    val sslContext = SSLContext.getInstance("TLS")
    sslContext.init(null, arrayOf(trustManager), SecureRandom())

    DynamicSSLContextHolder.updateSSLContext(sslContext, trustManager)
}

关键优势

  • 无需重建OkHttpClient或Retrofit实例,复用原有单例对象,避免不必要的资源消耗
  • 动态baseUrl和动态证书切换逻辑完全解耦,各自独立维护
  • 线程安全:通过@Volatile保证多线程环境下的配置可见性

内容的提问来源于stack exchange,提问作者Synth

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 19:35:57