You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

KMM项目Ktor客户端iOS端刷新token后不重试原请求如何解决

KMM Ktor iOS端Auth刷新令牌后不重试问题修复方案

核心原因

该问题是iOS端Ktor 1.x版本特性、协程调度差异和代码实现问题共同导致的,Android端因为JVM协程调度逻辑和Ktor引擎实现不同所以能正常运行。

常见触发点:

  • refresh令牌的接口未正确封装为Kotlin协程挂起函数,回调未执行就提前返回了空令牌
  • refresh接口误用了带Auth插件的HttpClient发起请求,陷入401校验死循环
  • 未显式配置bearer认证的loadTokens和sendWithoutRequest参数,iOS端无法识别新令牌
  • 令牌存储在Auth配置的局部变量中,刷新后的令牌无法被重试请求读取

修复步骤

1. 封装refresh接口为标准挂起函数

确保iOS端的refreshAuthToken方法用suspendCancellableCoroutine包装,协程会等待接口返回结果再往下执行,避免提前返回未初始化的令牌。
示例封装代码:

suspend fun refreshAuthToken(): Result<LoginResponse> = suspendCancellableCoroutine { cont ->
    // 必须用不带Auth插件的tokenClient调用refresh接口,避免死循环
    tokenClient.post<LoginResponse>(ApiEndPoints.RefreshToken.url) {
        contentType(ContentType.Application.Json)
        setBody(RefreshTokenRequest(refreshToken = TokenStorage.getRefreshToken()))
    }.invokeOnCompletion { throwable ->
        if (throwable != null) {
            cont.resume(Result.failure(throwable))
            return@invokeOnCompletion
        }
        val response = getCompleted()
        cont.resume(Result.success(response))
    }
}

2. 调整Auth插件配置

增加loadTokens配置,令牌统一存在跨平台KV存储中(可以使用multiplatform-settings或者DataStore实现),不要存在Auth配置的局部变量里,同时显式开启sendWithoutRequest参数。
修改后的iOS端HttpClient配置:

actual val httpClient: HttpClient = HttpClient {
    defaultRequest {
        host = ApiEndPoints.Base.url
        url {
            protocol = URLProtocol.HTTPS
        }
        contentType(ContentType.Application.Json)
        header(CONNECTION, CLOSE)
    }
    expectSuccess = false
    install(Auth) {
        bearer {
            // 从持久化存储读取当前有效令牌
            loadTokens {
                val accessToken = TokenStorage.getAccessToken()
                val refreshToken = TokenStorage.getRefreshToken()
                if (accessToken.isNotEmpty() && refreshToken.isNotEmpty()) {
                    BearerTokens(accessToken, refreshToken)
                } else null
            }
            // iOS端必须显式开启,否则新令牌不会自动携带到请求中
            sendWithoutRequest = true
            refreshTokens { _ ->
                NSLog("Unauthorized response received")
                val refreshResult = BaseAPIClass().refreshAuthToken()
                return@refreshTokens refreshResult.fold(
                    failed = {
                        NSLog("Token refresh failed: ${it.message}")
                        // 刷新失败清空令牌,触发登录逻辑
                        TokenStorage.clearTokens()
                        null
                    },
                    succeeded = { response ->
                        NSLog("Token refresh success")
                        // 刷新成功更新持久化存储的令牌
                        TokenStorage.saveTokens(
                            accessToken = response.accessToken ?: "",
                            refreshToken = response.refreshToken ?: ""
                        )
                        BearerTokens(
                            accessToken = response.accessToken ?: "",
                            refreshToken = response.refreshToken ?: ""
                        )
                    }
                )
            }
        }
    }
    install(JsonFeature) {
        val json = kotlinx.serialization.json.Json {
            ignoreUnknownKeys = true
            coerceInputValues = true
        }
        serializer = KotlinxSerializer(json)
    }
}

3. 可选升级方案

Ktor 1.x已经停止维护,2.x版本完全重构了iOS端的引擎和Auth插件逻辑,修复了大量跨平台兼容性问题,如果条件允许建议直接升级到Ktor 2.2.0以上版本,可避免该类问题。


内容的提问来源于stack exchange,提问作者Md Hanif

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 12:45:06