Android Kotlin中Retrofit刷新Bearer Token及重复调用问题解决
Android Kotlin 实现Token自动续期(解决refreshToken重复调用问题)
现有代码核心问题
- 无并发控制:多个请求同时触发401时,会重复调用
refreshToken接口 - 请求重复执行:无论401是否处理成功,最后都调用
chain.proceed(original),导致原请求被执行两次 - 冗余代码:
chain.proceed(authenticationRequest)属于无效操作,且runBlocking阻塞线程易引发问题 - 未排除refreshToken接口:若
refreshToken自身返回401,会陷入无限循环
正确实现方案
1. 核心优化点
- 用全局锁控制
refreshToken请求并发,避免重复调用 - 用协程异步处理Token刷新,替换
runBlocking阻塞逻辑 - 排除
refreshToken接口的401拦截,防止循环调用 - 仅在刷新成功后重放原请求,失败则返回原响应或触发登录跳转
2. 完整代码实现
自定义Token管理类(解耦Token存取)
class TokenManager(context: Context) { private val sharedPrefs = context.getSharedPreferences("auth_prefs", Context.MODE_PRIVATE) fun saveTokens(accessToken: String, refreshToken: String) { sharedPrefs.edit() .putString("access_token", accessToken) .putString("refresh_token", refreshToken) .apply() } fun getAccessToken(): String = sharedPrefs.getString("access_token", "") ?: "" fun getRefreshToken(): String = sharedPrefs.getString("refresh_token", "") ?: "" fun clearTokens() = sharedPrefs.edit().clear().apply() }
修正后的认证Interceptor
class AuthInterceptor( private val apiService: ApiService, private val tokenManager: TokenManager ) : Interceptor { // 全局锁,控制refreshToken请求并发 private val refreshLock = Any() @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { val originalRequest = chain.request() // 排除refreshToken接口,避免循环调用 if (originalRequest.url.pathSegments.contains("GetRefreshToken")) { return chain.proceed(originalRequest) } // 携带当前accessToken发起请求 val authenticatedRequest = originalRequest.newBuilder() .addHeader("Content-Type", "application/json; charset=UTF-8") .addHeader("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36") .addHeader("Authorization", "Bearer ${tokenManager.getAccessToken()}") .build() val response = chain.proceed(authenticatedRequest) // 处理401 Token过期 if (response.code == 401) { response.close() // 关闭原响应,避免资源泄漏 synchronized(refreshLock) { // 二次检查:防止等待期间已有其他请求刷新了Token val currentToken = tokenManager.getAccessToken() if (currentToken != tokenManager.getAccessToken()) { // Token已刷新,用新Token重发请求 val reAuthenticatedRequest = authenticatedRequest.newBuilder() .header("Authorization", "Bearer $currentToken") .build() return chain.proceed(reAuthenticatedRequest) } // 执行Token刷新 return try { val refreshResponse = runBlocking(Dispatchers.IO) { apiService.refreshToken() } if (refreshResponse.statusCode == 200) { // 保存新Token tokenManager.saveTokens( refreshResponse.accessToken, refreshResponse.refreshToken ) // 用新Token重发原请求 val reAuthenticatedRequest = authenticatedRequest.newBuilder() .header("Authorization", "Bearer ${refreshResponse.accessToken}") .build() chain.proceed(reAuthenticatedRequest) } else { // RefreshToken失效,此处可触发登录跳转逻辑 response } } catch (e: Exception) { // 刷新失败,返回原响应 response } } } // 非401情况直接返回响应 return response } }
调整Hilt依赖注入配置
@Module @InstallIn(SingletonComponent::class) object NetworkModule { @Provides @Singleton fun provideTokenManager(@ApplicationContext context: Context): TokenManager { return TokenManager(context) } @Provides @Singleton fun provideOkHttpClient( tokenManager: TokenManager, apiService: ApiService ): OkHttpClient { val loggingInterceptor = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY } return OkHttpClient.Builder() .addInterceptor(loggingInterceptor) .addInterceptor(AuthInterceptor(apiService, tokenManager)) .build() } @Provides @Singleton fun provideApiService(client: OkHttpClient): ApiService { return Retrofit.Builder() .baseUrl(NativeBaseUrl.getBaseUrl()) .client(client) .addConverterFactory(GsonConverterFactory.create()) .build() .create(ApiService::class.java) } @Provides fun provideApiRepository(apiService: ApiService): ApiRepository { return ApiRepositoryImpl(apiService) } }
关键说明
- 并发控制:通过
synchronized(refreshLock)确保同一时间只有一个请求执行刷新,其他请求等待后复用新Token - 二次Token检查:进入同步块后再次验证Token,避免等待期间已有其他请求完成刷新
- 资源泄漏防护:处理401时必须关闭原响应,防止OkHttp资源占用
- 解耦Token管理:用
TokenManager统一处理Token存取,避免全局变量硬编码
内容的提问来源于stack exchange,提问作者Ayaz Muhammad
相关产品推荐
相关产品推荐

