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
相关产品推荐
相关产品推荐

