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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 14:55:18