Ktor IO客户端如何防止refreshTokens回调被多次重复调用
Ktor Bearer Auth 避免重复刷新Token的锁实现方案
你需要使用 Kotlin 协程专属的非阻塞互斥锁 Mutex 结合双重校验机制,解决并发场景下refreshTokens回调重复触发的问题,该方案不会阻塞线程,仅挂起等待的协程,适配Ktor的协程运行环境。
实现代码示例
首先引入协程同步依赖(已引入可跳过),并声明和HttpClient实例同生命周期的单例互斥锁:
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock // 要和业务使用的HttpClient实例绑定,保证为单例 private val refreshTokenMutex = Mutex()
修改后的refreshTokens回调实现:
refreshTokens { val oldRefreshToken = preference.get(PreferenceKeys.REFRESH_TOKEN.key) // 第一层控制:同一时间仅允许一个协程进入刷新逻辑,其他协程挂起等待锁释放 refreshTokenMutex.withLock { // 第二层校验:拿到锁后先判断Token是否已经被其他请求刷新完成 val currentRefreshToken = preference.get(PreferenceKeys.REFRESH_TOKEN.key) if (currentRefreshToken != oldRefreshToken) { // 已被其他请求刷新,直接返回最新Token即可 return@withLock BearerTokens( accessToken = preference.get(PreferenceKeys.ACCESS_TOKEN.key).orEmpty(), refreshToken = currentRefreshToken.orEmpty() ) } // 未被刷新,执行实际的刷新请求逻辑 val fullUrl = "$baseUrl/auth/token:refresh" val authData = tokenClient.post<AuthResponse>(fullUrl) { body = AuthResponse(null, oldRefreshToken) header(HttpHeaders.ContentType, ContentType.Application.Json) } // 刷新成功后先持久化新Token到本地存储 preference.put(PreferenceKeys.ACCESS_TOKEN.key, authData.accessToken.orEmpty()) preference.put(PreferenceKeys.REFRESH_TOKEN.key, authData.refreshToken.orEmpty()) BearerTokens( accessToken = authData.accessToken ?: "", refreshToken = authData.refreshToken ?: "" ) } }
注意事项
refreshTokenMutex必须和业务使用的HttpClient实例绑定为单例,不要每次创建HttpClient都新建锁实例- 代码中的
tokenClient建议为单独配置的HttpClient实例,不要添加Auth插件,避免刷新Token的请求本身触发401校验,导致循环刷新 - 如果刷新Token时返回401等错误,说明refreshToken已失效,需要捕获异常主动清除本地Token,引导用户重新登录,避免锁被无效持有
- 如果是多进程场景下使用,需要额外实现跨进程锁(比如基于文件锁、SharedPreferences事务锁),协程Mutex仅支持同一进程内的协程互斥
内容的提问来源于stack exchange,提问作者Narek Simonyan
相关产品推荐
相关产品推荐

