共计 2096 个字符,预计需要花费 6 分钟才能阅读完成。
背景痛点:为什么 Token 在并发场景下容易失效?
在开发 Android 应用时,我们经常会遇到这样的场景:用户快速滑动列表触发分页加载,或者同时打开多个 Tab 发送并行请求。这时候如果 Token 突然过期,可能会导致:

- 所有并发请求都收到 401 未授权错误
- 应用频繁弹出登录界面打断用户操作
- 后台同时发起多个 Token 刷新请求造成服务端压力
传统单 Token 机制的核心问题在于:
- 竞态条件:当多个请求同时发现 Token 过期时,会各自尝试刷新 Token,导致服务端收到重复的刷新请求
- 时序混乱:后刷新的 Token 可能覆盖先刷新的 Token,使得部分请求仍然使用已失效的凭证
- 用户体验差:直接弹出登录框会打断用户当前操作流程
技术方案选型:三种主流解决方案对比
1. 单 Token 刷新机制
- 优点:实现简单,适合低并发场景
- 缺点:
- 无法处理并发刷新冲突
- 需要客户端精确同步 Token 过期时间
2. 请求队列方案
- 优点:能保证请求顺序执行
- 缺点:
- 增加请求延迟
- 需要维护复杂的队列状态
3. 双 Token 机制(推荐方案)
采用 AccessToken+RefreshToken 组合:
- AccessToken:短期有效(如 2 小时),用于业务请求认证
- RefreshToken:长期有效(如 7 天),专门用于获取新 AccessToken
工作流程:
- AccessToken 过期时,用 RefreshToken 获取新 AccessToken
- 获取成功后更新本地存储
- 失败的请求自动用新 Token 重试
代码实现:基于 OkHttp Interceptor 的完整解决方案
以下是核心拦截器实现代码(Kotlin):
class TokenRefreshInterceptor : Interceptor {
@Volatile private var isRefreshing = false
private val lock = Any()
override fun intercept(chain: Interceptor.Chain): Response {val request = chain.request()
var response = chain.proceed(withAccessToken(request))
// 检测到 Token 过期
if (response.code == HTTP_UNAUTHORIZED) {synchronized(lock) {if (isRefreshing) {
// 等待其他线程完成刷新
while (isRefreshing) {lock.wait()
}
return chain.proceed(withAccessToken(request))
}
isRefreshing = true
try {val newToken = refreshToken()
saveNewToken(newToken)
lock.notifyAll()
return chain.proceed(withAccessToken(request))
} finally {isRefreshing = false}
}
}
return response
}
private fun refreshToken(): String {
// 实现 RefreshToken 的获取逻辑
// 建议添加指数退避重试机制
}
}
关键点说明:
- 使用
@Volatile保证多线程可见性 - 同步块内双检锁避免重复刷新
wait()/notifyAll()实现线程通信- 失败请求自动重试无需用户干预
进阶优化:性能与安全考量
刷新策略优化
- 预刷新:在 Token 即将过期前主动刷新(需配合过期时间预测)
- 被动刷新:遇到 401 时再刷新(实现简单但体验稍差)
指数退避算法
当刷新失败时,建议按以下间隔重试:
- 首次失败:等待 1 秒
- 第二次失败:等待 2 秒
- 第三次失败:等待 4 秒
安全存储方案
val encryptedPrefs = EncryptedSharedPreferences.create(
"token_store",
MasterKey.Builder(context).build(),
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
避坑指南:常见问题与解决方案
- 不要直接在拦截器中弹登录窗:
- 使用 EventBus 或 LiveData 发送全局事件
-
由 BaseActivity 统一处理跳转逻辑
-
服务端原子性保证:
- 更新 AccessToken 时应使旧 Token 立即失效
-
可以考虑添加 jti(JWT ID)字段控制
-
跨进程共享方案:
- 使用 ContentProvider 封装 Token 访问
- 或者通过文件锁实现进程间同步
思考与实践
尝试回答以下问题来检验学习效果:
- 如何设计 Benchmark 来比较不同方案的性能差异?
- 当用户长时间不操作导致 RefreshToken 也过期时,应该如何优雅处理?
- 在 Jetpack Compose 架构下,Token 管理应该如何与 UI 层解耦?
建议在实际项目中添加性能监控,记录:
- Token 刷新成功率
- 平均等待时间
- 并发冲突发生率
这些数据可以帮助你持续优化认证流程。
正文完
