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

Android Retrofit如何实现Token过期自动刷新(401场景处理)

实现Retrofit请求401时自动刷新Token

我已经实现了通过POST请求(携带用户名、密码)获取响应中的token和refreshToken,并保存到SharedPreferences中。但token有效期只有10分钟,我希望在请求返回401状态码时自动获取新的token,现在毫无头绪。


已实现的相关代码

1. POST接口定义

interface APIInterface {
    @POST("api/Tokens")
    fun getAuthorizationToken(@Body tokenRequest: TokenRequest): Call<TokenResponse>

    @POST("api/Tokens/refresh")
    fun refreshAuthorizationToken(@Body refreshRequest: TokenRefreshRequest): Call<TokenResponse>
}

2. TokenManager(SharedPreferences管理类)

class TokenManager(context: Context) {

    private var prefs = context.getSharedPreferences(TOKEN_FILE, Context.MODE_PRIVATE)

    fun saveToken(token: String?, refreshToken: String?){
        val editor = prefs.edit()
        editor.apply {
            putString(USER_TOKEN, token)
            putString(REFRESH_TOKEN, refreshToken)
            putBoolean(IS_LOGGED_IN,true)
        }
        editor.apply()
        Log.d(TAG, "Shared preferences are added :$editor")
    }

    fun getToken(): String? {
       return prefs.getString(USER_TOKEN, null)
    }

    fun getRefreshToken(): String? {
        return prefs.getString(REFRESH_TOKEN, null)
    }
}

3. Retrofit构建器

object RetrofitBuilder {

    private val client = OkHttpClient.Builder().build()

    val retrofit by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(client)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    fun <T> buildService(service: Class<T>): T {
        return retrofit.create(service)
    }
}

4. 已编写但未使用的AuthInterceptor

class AuthInterceptor (): Interceptor{

    lateinit var tokenManager: TokenManager

    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request().newBuilder()
        val token = tokenManager.getToken()

        request.addHeader("Authorization", "Bearer $token")
        request.addHeader("Accept", "application/json")
        return chain.proceed(request.build())
    }
}

解决方案

步骤1:完善AuthInterceptor,添加401自动刷新逻辑

修改拦截器,在响应返回401时自动调用刷新token接口,获取新token后重新发起原请求:

class AuthInterceptor(private val context: Context) : Interceptor {
    private val tokenManager by lazy { TokenManager(context) }
    private val retrofit by lazy { RetrofitBuilder.getRetrofit(context) }

    override fun intercept(chain: Interceptor.Chain): Response {
        val originalRequest = chain.request()
        val currentToken = tokenManager.getToken()

        // 构造携带token的请求
        val authenticatedRequest = originalRequest.newBuilder()
            .addHeader("Authorization", "Bearer $currentToken")
            .addHeader("Accept", "application/json")
            .build()

        val response = chain.proceed(authenticatedRequest)

        // 处理401未授权状态
        if (response.code == 401) {
            response.close() // 关闭原响应防止资源泄漏

            val refreshToken = tokenManager.getRefreshToken() ?: return response
            // 同步调用刷新token接口(必须同步,否则异步会导致原请求流程中断)
            val refreshCall = retrofit.create(APIInterface::class.java)
                .refreshAuthorizationToken(TokenRefreshRequest(refreshToken))

            return try {
                val refreshResponse = refreshCall.execute()
                if (refreshResponse.isSuccessful) {
                    val newToken = refreshResponse.body()?.token
                    val newRefreshToken = refreshResponse.body()?.refreshToken

                    // 保存新的token和refreshToken到SP
                    newToken?.let {
                        tokenManager.saveToken(it, newRefreshToken)
                    }

                    // 用新token重新发起原请求
                    val newAuthenticatedRequest = originalRequest.newBuilder()
                        .header("Authorization", "Bearer $newToken")
                        .build()
                    chain.proceed(newAuthenticatedRequest)
                } else {
                    // 刷新token失败,返回原401响应(可在此处添加登出逻辑)
                    response
                }
            } catch (e: IOException) {
                // 网络异常,返回原响应
                response
            }
        }

        return response
    }
}

步骤2:更新RetrofitBuilder,注入拦截器

修改Retrofit构建逻辑,将AuthInterceptor添加到OkHttpClient中:

object RetrofitBuilder {
    private const val BASE_URL = "你的基础接口地址"

    fun getClient(context: Context): OkHttpClient {
        return OkHttpClient.Builder()
            .addInterceptor(AuthInterceptor(context))
            .build()
    }

    fun getRetrofit(context: Context): Retrofit {
        return Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(getClient(context))
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    fun <T> buildService(context: Context, service: Class<T>): T {
        return getRetrofit(context).create(service)
    }
}

关键注意事项

  • 同步刷新:必须用execute()同步调用刷新接口,不能用enqueue()异步执行,否则原请求的响应流程会提前结束。
  • 并发控制:如果有多个请求同时返回401,可能会触发多次刷新token请求,可添加同步锁(如synchronized)避免这种情况。
  • 失败处理:如果刷新token接口也返回失败(比如refreshToken过期),需要在此处清除本地token,跳转登录页面。
  • 参数匹配:确保TokenRefreshRequest类的字段和后端刷新接口的参数要求一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 11:05:18