注入 Koin 的 OkHttpClient 失去授权 header

OkHttpClient injected with Koin loses Authorization header

我正在将 DI 添加到现有项目,在此过程中我遇到了 header 授权从请求中消失的问题。 Retrofit/OkHttp 没有任何异常或日志。我的依赖项是:

implementation 'com.squareup.retrofit2:retrofit:2.6.0'
implementation 'com.squareup.okhttp:okhttp:2.7.5'
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
implementation 'org.koin:koin-android:2.1.3'

我使用 provideClient 创建 http 客户端:

class OkHttpProvider private constructor() {

    companion object {

        fun provideClient(credentials: UsernamePasswordCredentials? = null, context: Context): OkHttpClient {
            val client = OkHttpClient.Builder()
            // logs
            if (BuildConfig.DEBUG) {
                client.addInterceptor(
                        HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
                )
            }

            if (credentials != null) {
                val creds = Credentials.basic(credentials.userName, credentials.password)
                val headerInterceptor = Interceptor { chain ->
                    var request = chain.request()
                    val headers = request
                            .headers()
                            .newBuilder()
                            .add("Authorization", creds)
                            .build()
                    request = request.newBuilder().headers(headers).build()
                    chain.proceed(request)
                }
                //client.addInterceptor(AccessTokenInterceptor(credentials))
                client.addInterceptor(headerInterceptor)
            }

            client
                    .callTimeout(60L, TimeUnit.SECONDS)
                    .connectTimeout(10L, TimeUnit.SECONDS)
                    .readTimeout(60L, TimeUnit.SECONDS)
                    .writeTimeout(60L, TimeUnit.SECONDS)
                    .sslSocketFactory(getSslContext().socketFactory).hostnameVerifier { _, _ -> true }

            client.addInterceptor(ChuckInterceptor(context))

            return client.build()
        }

        private fun getSslContext(): SSLContext {
            ...implementation...
        }
    }
}

我的 http 客户端和 Retrofit 模块如下:

object HttpClientModule {
    val module = module {
        single(named(COMMON)) {
            OkHttpProvider.provideClient(
                    get<SharedPreferenceManager>().getUserCredentials(),
                    androidContext()
            )
        }
        ...other versions...
    }

    const val COMMON = "common"
}

object ApiModule {
    val module = module {
        single {
            RetrofitFactory.getServiceInstance(
                    ApiService::class.java,
                    get<SharedPreferenceManager>().getString(LocalDataSource.BUILD_OPTION_API, ""),
                    get(named(HttpClientModule.COMMON))
            )
        }
        ...other apis...
    }
}

object RetrofitFactory {
    const val GEO_URL = "http://maps.googleapis.com/maps/api/"

    fun <T> getServiceInstance(
            clazz: Class<T>,
            url: String = GEO_URL,
            client: OkHttpClient
    ): T = getRetrofitInstance(url, client).create(clazz)

    private fun getRetrofitInstance(
            url: String,
            client: OkHttpClient
    ) = Retrofit.Builder()
            .baseUrl(url)
            .addConverterFactory(GsonConverterFactory.create())
            .client(client)
            .addCallAdapterFactory(CoroutineCallAdapterFactory())
            .build()
}

当用户使用 phone 开始登录并使用“admin”授权 header 发送短信和请求时,应用程序开始使用“admin”用户并在共享首选项中保存一些凭据,当用户从短信输入代码时,他的新用户凭据将保存在共享首选项中。在该应用程序发送两个请求后,授权 header 未出现在其中。我在 Chuck 上看到了,我什至用 Charles 重新检查了它。

为了解决这个问题,我尝试了几种解决方案。首先,我将 http 客户端的注入从 single 更改为 factory,但没有用。其次,我google了一下这个问题,没有提到这个现象。第三,我根据 this 文章写了 AccessTokenInterceptor 并且还用日志覆盖了所有内容。我注意到拦截器在正常情况下工作正常,但是当缺少授权 header 时,方法 intercept 不会被调用。这可能是默认 headerInterceptor 也不起作用的原因。第四,我升级了Retrofit和OkHttp的版本,这也没有帮助。

我注意到关于那个错误的有趣事情:如果我在 Retrofit 失去授权后重新启动应用程序 header,应用程序工作正常测试用户使用正确的令牌正确登录。任何不重新启动应用程序而重新登录的尝试都会失败。也许有人有类似的问题或知道这里发生了什么,欢迎任何想法。

我终于找到了解决这个问题的方法。问题是用户凭据在创建时仅传递给 provideClient 一次。在那一刻,用户以管理员身份登录,标准用户凭据为空,因此 ApiService 的 http 客户端是在未经授权 header.

的情况下创建的

为了解决这个问题,我更改了 AccessTokenInterceptor 形式 articleHttpClientType 是 select 的枚举,需要使用凭据):

class AccessTokenInterceptor(
        private val sharedPreferenceManager: SharedPreferenceManager,
        private val clientType: OkHttpProvider.HttpClientType
) : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        val credentials = getUserCredentials(clientType)
        if (credentials != null) {
            val accessToken = Credentials.basic(credentials.userName, credentials.password)
            val request = newRequestWithAccessToken(chain.request(), accessToken)

            return chain.proceed(request)
        } else {
            return chain.proceed(chain.request())
        }
    }

    private fun getUserCredentials(clientType: OkHttpProvider.HttpClientType): UsernamePasswordCredentials? {
        return when (clientType) {
            OkHttpProvider.HttpClientType.COMMON -> sharedPreferenceManager.getUserCredentials()
            OkHttpProvider.HttpClientType.ADMIN -> ServiceCredentialsUtils.getCredentials(sharedPreferenceManager)
        }
    }

    private fun newRequestWithAccessToken(@NonNull request: Request, @NonNull accessToken: String): Request {
        return if (request.header("Authorization") == null) {
            request.newBuilder()
                    .header("Authorization", accessToken)
                    .build()
        } else {
            request
        }
    }
}

现在每次发送请求时,Interceptor 获取用户的凭据并将 header 添加到请求中。