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

Retrofit & RxJava2实现401自动刷新Token并重试请求方案咨询

这个场景太常见了,我在做App的时候几乎每个项目都要处理这个逻辑——Retrofit请求遇到401自动刷token并重试,核心就是利用OkHttp的拦截器来做全局拦截,还要注意并发请求的情况,避免重复调用刷新接口。下面给你详细讲怎么实现:

核心思路
  1. 用OkHttp的Interceptor拦截所有出站请求,给请求带上当前有效的authToken
  2. 当收到401响应时,判断是不是token过期导致的(排除刷新接口本身的401)
  3. 触发刷新token的请求,更新本地存储的authToken和refreshToken
  4. 用新的authToken重新发起原请求
  5. 处理并发场景:多个请求同时遇到401时,只发起一次刷新请求,其他请求等待刷新完成后用新token重试
具体代码实现

首先定义你的刷新token接口和数据类:

// 刷新Token的API接口
interface AuthService {
    @POST("/auth/actions/refresh")
    suspend fun refreshToken(@Body request: RefreshTokenRequest): Response<TokenResponse>
}

// 请求体
data class RefreshTokenRequest(val refreshToken: String)
// 响应体
data class TokenResponse(val authToken: String, val refreshToken: String)

然后写一个自定义的Token管理类,负责存储和读取token:

class TokenManager(private val sharedPrefs: SharedPreferences) {
    // 获取当前的authToken
    fun getAuthToken(): String = sharedPrefs.getString("auth_token", "") ?: ""
    // 获取refreshToken
    fun getRefreshToken(): String = sharedPrefs.getString("refresh_token", "") ?: ""
    // 保存新的authToken
    fun saveAuthToken(token: String) = sharedPrefs.edit().putString("auth_token", token).apply()
    // 保存新的refreshToken
    fun saveRefreshToken(token: String) = sharedPrefs.edit().putString("refresh_token", token).apply()
    // 清除所有token(刷新失败时用)
    fun clearTokens() = sharedPrefs.edit().remove("auth_token").remove("refresh_token").apply()
}

接下来是核心的拦截器实现:

class AuthInterceptor(
    private val authService: AuthService,
    private val tokenManager: TokenManager
) : Interceptor {

    // 用锁和标志位控制并发刷新
    private val refreshLock = Object()
    private var isRefreshing = false

    override fun intercept(chain: Interceptor.Chain): Response {
        var originalRequest = chain.request()
        val currentAuthToken = tokenManager.getAuthToken()

        // 给原请求添加Authorization头
        originalRequest = originalRequest.newBuilder()
            .header("Authorization", "Bearer $currentAuthToken")
            .build()

        val originalResponse = chain.proceed(originalRequest)

        // 检查是否是401,并且不是刷新token的请求本身(避免死循环)
        if (originalResponse.code == 401 && !originalRequest.url.pathSegments.contains("refresh")) {
            synchronized(refreshLock) {
                // 双重检查:防止等待锁的过程中已经有其他线程刷新了token
                val newAuthToken = tokenManager.getAuthToken()
                if (newAuthToken != currentAuthToken) {
                    // 已经有新token了,直接用新token重试原请求
                    val retryRequest = originalRequest.newBuilder()
                        .header("Authorization", "Bearer $newAuthToken")
                        .build()
                    return chain.proceed(retryRequest)
                }

                // 没有新token,开始刷新
                if (!isRefreshing) {
                    isRefreshing = true
                    // 因为intercept是同步方法,用runBlocking调用挂起的刷新接口
                    runBlocking {
                        val refreshResponse = authService.refreshToken(
                            RefreshTokenRequest(tokenManager.getRefreshToken())
                        )
                        if (refreshResponse.isSuccessful) {
                            refreshResponse.body()?.let { tokenResp ->
                                // 更新本地token
                                tokenManager.saveAuthToken(tokenResp.authToken)
                                tokenManager.saveRefreshToken(tokenResp.refreshToken)
                            }
                        } else {
                            // 刷新失败:清除token,跳转到登录页(这里根据你的业务处理)
                            tokenManager.clearTokens()
                            // 可以抛异常或者返回原响应,让上层处理
                            return@runBlocking originalResponse
                        }
                    }
                    isRefreshing = false
                }
            }

            // 刷新完成后,用新token重试原请求
            val finalAuthToken = tokenManager.getAuthToken()
            val finalRetryRequest = originalRequest.newBuilder()
                .header("Authorization", "Bearer $finalAuthToken")
                .build()
            return chain.proceed(finalRetryRequest)
        }

        // 不是401,直接返回原响应
        return originalResponse
    }
}

最后把拦截器添加到OkHttp客户端,再构建Retrofit:

// 初始化TokenManager
val tokenManager = TokenManager(context.getSharedPreferences("auth_prefs", Context.MODE_PRIVATE))

// 初始化AuthService(用来调用刷新接口)
val authService = Retrofit.Builder()
    .baseUrl(BASE_URL)
    .addConverterFactory(GsonConverterFactory.create())
    .build()
    .create(AuthService::class.java)

// 构建带拦截器的OkHttp客户端
val okHttpClient = OkHttpClient.Builder()
    .addInterceptor(AuthInterceptor(authService, tokenManager))
    .build()

// 构建你的业务API服务
val yourApiService = Retrofit.Builder()
    .baseUrl(BASE_URL)
    .client(okHttpClient)
    .addConverterFactory(GsonConverterFactory.create())
    .addCallAdapterFactory(CoroutineCallAdapterFactory()) // 如果用协程的话
    .build()
    .create(YourBusinessApi::class.java)
关键注意点
  • 避免死循环:一定要排除刷新token的请求本身,不然如果刷新接口也返回401,会无限触发刷新逻辑
  • 并发处理:用synchronized和isRefreshing标志位,确保同时多个401请求只会发起一次刷新,避免重复调用刷新接口
  • 双重检查token:在进入同步块后再次检查token,防止等待锁的过程中其他线程已经完成了刷新
  • 失败处理:刷新token失败(比如refreshToken也过期了),要及时清除本地token,跳转到登录页面,避免无限重试
  • 协程适配:因为拦截器的intercept是同步方法,所以用runBlocking来调用挂起的刷新接口;如果用RxJava,处理逻辑类似,换成Observable的操作符即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 10:07:34