Ktor Client获取Access Token后正确设置及即时请求无token问题解决
Ktor Client 运行时Token不携带问题解决方案
问题重现
我的Ktor Client配置如下:
@OptIn(ExperimentalSerializationApi::class) @Singleton @Provides fun provideHttpClient( localService: LocalService, ): HttpClient { val chuckerInterceptor = ChuckerInterceptor.Builder(App.appContext) .collector(ChuckerCollector(App.appContext)) .maxContentLength(250000L) .redactHeaders(emptySet()) .alwaysReadResponseBody(false) .build() val okhttpEngine = OkHttp.create { addInterceptor(chuckerInterceptor) } return HttpClient(okhttpEngine) { install(ContentNegotiation) { json( Json { prettyPrint = true isLenient = true useAlternativeNames = true ignoreUnknownKeys = true encodeDefaults = false explicitNulls = false } ) } install(HttpTimeout) { requestTimeoutMillis = 10_000 connectTimeoutMillis = 10_000 socketTimeoutMillis = 10_000 } install(Logging) { logger = object : Logger { override fun log(message: String) { Log.v("Logger Ktor =>", message) } } level = LogLevel.ALL } install(ResponseObserver) { onResponse { response -> Log.d("HTTP status:", "${response.status.value}") } } install(DefaultRequest) { header(HttpHeaders.ContentType, ContentType.Application.Json) } defaultRequest { contentType(ContentType.Application.Json) accept(ContentType.Application.Json) } install(Auth) { bearer { refreshTokens { val token = client.get { markAsRefreshTokenRequest() url(BASE_URL + REFRESH_TOKEN) parameter("refreshToken", localService.getRefreshToken()) }.body<AuthResponseDto>() BearerTokens( accessToken = token.accessToken.token, refreshToken = token.refreshToken.token ) } loadTokens { BearerTokens( accessToken = localService.getAccessToken().orEmpty(), refreshToken = localService.getRefreshToken().orEmpty(), ) } } } } }
认证成功后,已将Access Token和Refresh Token保存到DataStore,但后续请求头里不携带Token;重启应用后请求又能正常携带Token。推测是Ktor无法在运行时从本地存储更新Client配置,求解决办法。
解决方案
问题核心是loadTokens默认仅在Client初始化时执行一次,后续不会主动重新读取本地存储的最新Token。按以下步骤调整即可解决:
1. 让loadTokens每次请求都读取最新Token
Ktor的Bearer认证中,loadTokens是一个可执行的lambda,每次需要Token时都会触发它——但前提是你的localService方法能实时返回最新值。
- 检查
LocalService的getAccessToken()和getRefreshToken()实现,确保它们不是缓存旧值,而是每次调用都直接从DataStore读取最新数据。 - 如果DataStore的读取是挂起函数,需将
loadTokens改成挂起形式:
loadTokens suspend { BearerTokens( accessToken = localService.getAccessToken().await().orEmpty(), refreshToken = localService.getRefreshToken().await().orEmpty() ) }
2. 保存Token后手动更新Client的Token
如果需要在保存Token后立即生效,可以在保存完成后手动设置BearerToken:
- 初始化HttpClient时保留BearerAuth的引用:
val bearerAuth = install(Auth) { bearer { // 原有refreshTokens、loadTokens配置保持不变 } }
- 在Token保存到DataStore后,调用:
bearerAuth.bearerTokens = BearerTokens( accessToken = newAccessToken, refreshToken = newRefreshToken )
这会强制Client立即使用新Token,无需等待下次loadTokens调用。
3. 清理重复的DefaultRequest配置
你同时使用了install(DefaultRequest)和defaultRequest块,两者功能重复,建议保留其中一个即可,避免潜在的配置冲突。
内容的提问来源于stack exchange,提问作者Yarik Nikolenko
相关产品推荐
相关产品推荐

