Hilt场景下为Retrofit注入用户凭据实现BasicAuth认证的问题咨询
解决方案
问题根因
你当前的写法不符合Hilt的依赖注入生命周期规则:
- 你将
OkHttpClient、Retrofit、ApiService都声明为@Singleton,绑定到全局的SingletonComponent,这些实例会在App启动阶段就完成初始化,此时用户还未输入用户名密码,自然无法为provideRetrofitClient提供所需的两个String参数,因此编译器会抛出注入失败的错误。 - 全局单例的ApiService也无法在每次登录时动态替换底层Client,需要换动态传递凭据的实现思路。
推荐实现:可动态更新凭据的拦截器方案
该方案不需要重建Retrofit实例,开销极低,支持随时更新用户凭据,适配绝大多数业务场景。
第一步:自定义支持动态设置凭据的Basic认证拦截器
import okhttp3.Credentials import okhttp3.Interceptor import okhttp3.Response class BasicAuthInterceptor : Interceptor { // 用volatile保证多线程操作的可见性 @Volatile var username: String? = null @Volatile var password: String? = null override fun intercept(chain: Interceptor.Chain): Response { val originalRequest = chain.request() val requestBuilder = if (!username.isNullOrEmpty() && !password.isNullOrEmpty()) { // 调用okhttp提供的工具类生成标准Basic认证头 val authCredential = Credentials.basic(username!!, password!!) originalRequest.newBuilder() .header("Authorization", authCredential) } else { originalRequest.newBuilder() } return chain.proceed(requestBuilder.build()) } }
第二步:修改NetworkModule配置
注意补全你原来代码中缺失的@Module注解:
@Module @InstallIn(SingletonComponent::class) object NetworkModule { @Singleton @Provides fun provideGsonBuilder(): Gson = GsonBuilder().setLenient().create() // 提供自定义拦截器的全局单例 @Singleton @Provides fun provideBasicAuthInterceptor(): BasicAuthInterceptor = BasicAuthInterceptor() @Singleton @Provides fun provideRetrofitClient(authInterceptor: BasicAuthInterceptor): OkHttpClient { return OkHttpClient.Builder() .addInterceptor(authInterceptor) .readTimeout(60, TimeUnit.SECONDS) .connectTimeout(60, TimeUnit.SECONDS) .build() } @Singleton @Provides fun provideRetrofitBuilder(gsonBuilder: Gson, client: OkHttpClient): Retrofit.Builder { return Retrofit.Builder() .baseUrl(Constants.BASE_URL) .addConverterFactory(GsonConverterFactory.create(gsonBuilder)) .client(client) } @Singleton @Provides fun provideApiService(retrofitBuilder: Retrofit.Builder): ApiService { return retrofitBuilder .build() .create(ApiService::class.java) } }
第三步:修改ViewModel,登录前更新拦截器凭据
@HiltViewModel class AuthenticationViewModel @Inject constructor( private val apiService: ApiService, // 注入自定义拦截器实例 private val authInterceptor: BasicAuthInterceptor ) : ViewModel() { fun performLogin(username:String,password:String){ viewModelScope.launch { // 先更新拦截器的凭据,后续所有请求都会自动携带认证头 authInterceptor.username = username authInterceptor.password = password // 直接调用登录接口即可 apiService.login() } } }
注意事项
- 退出登录时记得将
authInterceptor的username和password置空,避免后续请求携带无效认证头 - 如果业务需要严格区分认证/非认证接口,可通过
@Qualifier注解提供两套OkHttpClient/ApiService实例,分别用于认证和非认证场景
内容的提问来源于stack exchange,提问作者Michael m
相关产品推荐
相关产品推荐

