Android Java中使用Retrofit处理JWT刷新令牌问题咨询
Android JWT Token过期处理方案
针对你遇到的JWT Token过期问题,结合你提到的两种思路,下面给出具体实现方案,适配你的Retrofit单例和拦截器场景:
方案一:提前刷新Token
在每次请求发起前,检查access token的剩余有效期,当距离过期还有设定阈值(比如30秒)时,自动调用刷新接口获取新token,再执行原请求。
实现步骤:
- 确保你已将access token的过期时间存储在本地(比如
SharedPreferences) - 在OkHttp拦截器中添加过期检查逻辑:
class TokenInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val authPref = AppContext.getSharedPreferences("auth", Context.MODE_PRIVATE) val currentAccessToken = authPref.getString("access_token", "") val expireTimestamp = authPref.getLong("expire_time", 0) val currentTime = System.currentTimeMillis() // 距离过期不足30秒时触发刷新 if (expireTimestamp - currentTime < 30 * 1000 && expireTimestamp > currentTime) { // 同步调用刷新接口,避免并发请求重复触发刷新 val newTokenInfo = refreshTokenSync() newTokenInfo?.let { authPref.edit() .putString("access_token", it.accessToken) .putLong("expire_time", currentTime + it.expiresIn * 1000) .apply() } } // 构建带新token的请求 val finalRequest = chain.request().newBuilder() .addHeader("Authorization", "Bearer ${authPref.getString("access_token", "")}") .build() return chain.proceed(finalRequest) } // 同步调用刷新接口(复用你的Retrofit单例) private fun refreshTokenSync(): TokenResponse? { return try { val refreshToken = AppContext.getSharedPreferences("auth", Context.MODE_PRIVATE) .getString("refresh_token", "") RetrofitSingleton.apiService.refreshToken(refreshToken).execute().body() } catch (e: Exception) { e.printStackTrace() null } } } - 额外优化:可以加一个全局锁或者标记位,防止多个并发请求同时触发刷新操作。
方案二:401错误触发Token刷新
当接口返回401 Unauthorized时,拦截该响应,调用刷新接口获取新token,然后自动重试原请求。
实现步骤:
- 在OkHttp拦截器中处理401响应:
class TokenInterceptor : Interceptor { private val refreshLock = Any() private var isRefreshing = false override fun intercept(chain: Interceptor.Chain): Response { val originalRequest = chain.request() val response = chain.proceed(addTokenToRequest(originalRequest)) // 处理401错误 if (response.code == 401) { synchronized(refreshLock) { if (!isRefreshing) { isRefreshing = true val authPref = AppContext.getSharedPreferences("auth", Context.MODE_PRIVATE) val refreshToken = authPref.getString("refresh_token", "") val tokenResponse = RetrofitSingleton.apiService.refreshToken(refreshToken).execute().body() tokenResponse?.let { // 更新本地存储的token信息 authPref.edit() .putString("access_token", it.accessToken) .putLong("expire_time", System.currentTimeMillis() + it.expiresIn * 1000) .putString("refresh_token", it.refreshToken) // 如果接口返回新的refresh token .apply() // 关闭原响应,用新token重试请求 response.close() return chain.proceed(addTokenToRequest(originalRequest)) } ?: run { // 刷新失败,清空本地token并跳转登录页 authPref.edit().clear().apply() AppContext.startActivity(Intent(AppContext, LoginActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK }) } isRefreshing = false } else { // 等待刷新完成后,用新token重试原请求 return chain.proceed(addTokenToRequest(originalRequest)) } } } return response } private fun addTokenToRequest(request: Request): Request { val accessToken = AppContext.getSharedPreferences("auth", Context.MODE_PRIVATE) .getString("access_token", "") return request.newBuilder() .addHeader("Authorization", "Bearer $accessToken") .build() } } - 注意事项:
- 加锁避免多个401请求同时触发刷新
- 若refresh token也过期,直接清空本地数据并跳转登录
- 确保刷新接口本身不会返回401,否则会陷入死循环
两种方案对比
- 提前刷新:用户体验更流畅,无请求重试延迟,但依赖本地时间准确性,需处理设备时间偏差问题
- 401重试:实现更简单,无需维护时间校验逻辑,但会有一次请求失败后重试的过程,可能影响瞬时体验
内容的提问来源于stack exchange,提问作者Nagendrakumar Boorapu
相关产品推荐
相关产品推荐

