Kotlin Multiplatform中非阻塞刷新Access Token的正确方式
KMP+Ktor 非阻塞Token刷新实现方案
你担心的情况确实会发生——如果用阻塞方式占满所有可用线程等待Token刷新,百分百会触发死锁:没有空闲线程去执行刷新任务,整个流程直接卡住。下面是用协程实现的非阻塞方案,完全替代runBlocking、Mutex、Lock:
核心逻辑
利用协程的挂起特性+原子变量跟踪刷新状态,做到:
- 多个并发请求遇Token过期时,只会触发一次刷新
- 等待的请求仅挂起而非阻塞线程,线程可继续处理其他任务(包括刷新任务)
1. 全局Token管理类(KMP共享模块)
用AtomicReference原子变量管理刷新任务,避免重复触发:
import kotlinx.coroutines.Deferred import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import java.util.concurrent.atomic.AtomicReference class TokenManager { // 原子引用:跟踪当前是否有正在进行的刷新任务 private val refreshJobRef = AtomicReference<Deferred<String?>?>(null) private var currentToken: String? = "初始过期Token" suspend fun getValidToken(): String { // 先检查当前Token是否有效,有效直接返回 currentToken?.takeIf { !isTokenExpired(it) }?.let { return it } // 尝试获取或启动唯一的刷新任务 val refreshJob = refreshJobRef.get() ?: run { val newRefreshJob = coroutineScope { async { try { // 替换成你的实际刷新Token接口请求(Ktor实现) val newToken = fetchNewTokenFromApi() currentToken = newToken newToken } finally { // 刷新完成后清空原子引用,允许下次触发刷新 refreshJobRef.compareAndSet(newRefreshJob, null) } } } // 原子更新引用,避免多线程同时启动刷新 if (refreshJobRef.compareAndSet(null, newRefreshJob)) newRefreshJob else refreshJobRef.get() } // 挂起等待刷新完成,返回新Token return refreshJob.await() ?: error("Token刷新失败") } // 实际调用刷新Token的API(Ktor请求) private suspend fun fetchNewTokenFromApi(): String { // 示例:return ktorClient.post("https://your-api.com/auth/refresh") { ... }.body() return "新的有效Token" } // 实现Token过期判断逻辑(比如解析JWT过期时间) private fun isTokenExpired(token: String): Boolean { return true // 替换成实际判断逻辑 } }
2. Ktor请求拦截器(KMP共享模块)
在Ktor客户端配置中添加拦截器,自动处理Token过期重试:
import io.ktor.client.HttpClient import io.ktor.client.plugins.HttpRequestRetry import io.ktor.client.plugins.auth.Auth import io.ktor.client.plugins.auth.providers.BearerAuthProvider import io.ktor.client.plugins.auth.providers.bearer import io.ktor.http.HttpHeaders fun createAuthHttpClient(tokenManager: TokenManager): HttpClient { return HttpClient { install(Auth) { bearer { // 每次请求前自动获取有效Token loadTokens { val validToken = tokenManager.getValidToken() BearerTokens(accessToken = validToken, refreshToken = "你的RefreshToken") } // 收到401时自动触发刷新,并重试请求 refreshTokens { val newToken = tokenManager.getValidToken() BearerTokens(accessToken = newToken, refreshToken = "更新后的RefreshToken") } } } // 可选:添加重试逻辑,确保刷新后重试请求 install(HttpRequestRetry) { retryOnServerErrors(maxRetries = 1) exponentialDelay() } } }
关键优势
- 彻底避免死锁:协程挂起不会占用线程,线程可自由处理刷新任务
- 无重复请求:原子变量确保同一时间只有一个刷新任务在运行
- 全平台兼容:纯Kotlin代码,直接在Android和iOS上运行
内容的提问来源于stack exchange,提问作者DarkSatyr
相关产品推荐
相关产品推荐

