刷新令牌在 ktor 中没有按预期工作

RefreshToken not working as expecting in ktor

嘿,我在 KMM 的 Ktor 工作。我尝试根据建议在我的应用程序中使用 进行身份验证。

HttpClient.kt

package com.example.kotlinmultiplatformsharedmodule

import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.auth.*
import io.ktor.client.plugins.auth.providers.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
import java.util.concurrent.TimeUnit

actual fun httpClient(config: HttpClientConfig<*>.() -> Unit) = HttpClient(OkHttp) {
    config(this)
    install(Logging) {
        logger = Logger.SIMPLE
        level = LogLevel.BODY
    }
    install(ContentNegotiation) {
        json(Json {
            prettyPrint = true
            ignoreUnknownKeys = true
            explicitNulls = false
        })
    }
    engine {
        config {
            retryOnConnectionFailure(true)
            connectTimeout(30, TimeUnit.SECONDS)
            readTimeout(40, TimeUnit.SECONDS)
        }
    }
    defaultRequest {
        header("Client-Version", Platform().versionCode)
    }
    install(Auth) {
        bearer {
            loadTokens {
                BearerTokens(tokenProvider.accessToken, "")
            }
            refreshTokens {
                val response =
                    client.post("https://vivek-modi.com/api/v1/session/refresh") {
                        contentType(ContentType.Application.Json)
                        setBody(KtorSessionCommand(tokenProvider.refreshToken))
                    }
                if (response.status == HttpStatusCode.Unauthorized) {
                    println("application will logout")
                    null
                } else {
                    println("application in else part")
                    val ktorLoginResponse = response.body<KtorLoginResponse>()
                    ktorLoginResponse.ktorAccessToken?.let { ktorAccessToken ->
                        ktorAccessToken.accessToken?.let { accessToken ->
                            ktorAccessToken.refreshToken?.let { refreshToken ->
                                BearerTokens(accessToken, refreshToken)
                            }
                        }
                    }
                }
            }
        }
    }
}

build.gradle.kts

plugins {
    kotlin("multiplatform")
    kotlin("native.cocoapods")
    id("com.android.library")
    id("kotlinx-serialization")
}

version = "1.0"

kotlin {
    android()
    iosX64()
    iosArm64()
    iosSimulatorArm64()

    cocoapods {
        summary = "Some description for the Shared Module"
        homepage = "Link to the Shared Module homepage"
        ios.deploymentTarget = "14.1"
        framework {
            baseName = "kotlinmultiplatformsharedmodule"
        }
    }

    sourceSets {
        val ktorVersion = "2.0.0"
        val commonMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-core:$ktorVersion")
                implementation("io.ktor:ktor-client-logging:$ktorVersion")
                implementation("io.ktor:ktor-server-default-headers:$ktorVersion")
                implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
                implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
                implementation("io.ktor:ktor-client-auth:$ktorVersion")
                implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2")
                implementation("io.insert-koin:koin-core:3.2.0-beta-1")
            }
        }
        val commonTest by getting {
            dependencies {
                implementation(kotlin("test"))
            }
        }
        val androidMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-okhttp:$ktorVersion")
                implementation("io.ktor:ktor-client-logging-jvm:$ktorVersion")
            }
        }
        val androidTest by getting
        val iosX64Main by getting
        val iosArm64Main by getting
        val iosSimulatorArm64Main by getting
        val iosMain by creating {
            dependsOn(commonMain)
            iosX64Main.dependsOn(this)
            iosArm64Main.dependsOn(this)
            iosSimulatorArm64Main.dependsOn(this)
            dependencies {
                implementation("io.ktor:ktor-client-darwin:$ktorVersion")
                implementation("io.ktor:ktor-client-logging-native:$ktorVersion")
            }
        }
        val iosX64Test by getting
        val iosArm64Test by getting
        val iosSimulatorArm64Test by getting
        val iosTest by creating {
            dependsOn(commonTest)
            iosX64Test.dependsOn(this)
            iosArm64Test.dependsOn(this)
            iosSimulatorArm64Test.dependsOn(this)
        }
    }
}

android {
    compileSdk = 32
    sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
    defaultConfig {
        minSdk = 21
        targetSdk = 32
    }
}

预期输出

场景一

when my main api return 401, I need to call https://vivek-modi.com/api/v1/session/refresh if this return different then 401 status, then I need to call my main api again.

场景二

when my main api return 401, I need to call https://vivek-modi.com/api/v1/session/refresh if this return 401, I need to logout my application.

实际输出

我在点击按钮时调用 api。

  1. 所以当我第一次点击按钮时,它调用了我的主 api,return 401。所以 refreshToken 调用 https://vivek-modi.com/api/v1/session/refresh return 401. 我使用 println 在控制台中打印消息,但它没有打印消息。

  1. 当我在按钮上单击第二次或更多次时。它只调用我的 main api。它没有调用我的刷新 api.

有人可以指导我吗?我怎样才能达到我的预期输出。

问题是客户端(特别是 Auth 插件)试图无限刷新令牌。这是相应问题的comment

When refresh token request fails with 401 it tries to refresh token again, resulting in an infinite cycle. Since token refreshing is a users' code, users need to mark such requests, so we can have a special case.

要解决它,您需要在请求生成器中调用 markAsRefreshTokenRequest()

val response =
    client.post("https://vivek-modi.com/api/v1/session/refresh") {
        markAsRefreshTokenRequest()
        contentType(ContentType.Application.Json)
        setBody(KtorSessionCommand(tokenProvider.refreshToken))
    }