feat: 添加 Ktor 客户端认证插件以增强 API 安全性

- 在 build.gradle.kts 中新增 Ktor 客户端认证依赖。
- 更新 ApiService 类,使用 Ktor 内置的 Auth 插件实现 Bearer token 自动管理和刷新逻辑,提升 API 调用的安全性和稳定性。
This commit is contained in:
penghanyuan
2026-02-14 14:07:21 +01:00
parent 4299a5b7bf
commit 80fa0be8f6
3 changed files with 48 additions and 8 deletions

View File

@@ -137,6 +137,7 @@ dependencies {
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.ktor.client.logging)
implementation(libs.ktor.client.auth)
// Room
implementation(libs.androidx.room.runtime)

View File

@@ -1,11 +1,12 @@
package com.huaga.life_echo.network
import com.huaga.life_echo.data.auth.TokenManager
import com.huaga.life_echo.network.interceptors.AuthInterceptorPlugin
import com.huaga.life_echo.network.models.*
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.android.*
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.plugins.HttpTimeout
@@ -19,8 +20,8 @@ import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
class ApiService(
tokenManager: TokenManager? = null,
authService: AuthService? = null
private val tokenManager: TokenManager? = null,
private val authService: AuthService? = null
) {
private val client = HttpClient(Android) {
install(ContentNegotiation) {
@@ -37,11 +38,48 @@ class ApiService(
socketTimeoutMillis = 45_000 // 读写超时
}
// 如果提供了tokenManager和authService安装认证拦截器
// 使用Ktor内置Auth插件自动添加Bearer token401时自动刷新并重试
if (tokenManager != null && authService != null) {
install(AuthInterceptorPlugin) {
this.tokenManager = tokenManager
this.authService = authService
install(Auth) {
bearer {
loadTokens {
val access = tokenManager.getAccessToken()
val refresh = tokenManager.getRefreshToken()
if (!access.isNullOrBlank() && !refresh.isNullOrBlank()) {
BearerTokens(access, refresh)
} else null
}
refreshTokens {
val refresh = oldTokens?.refreshToken
?: tokenManager.getRefreshToken()
if (!refresh.isNullOrBlank()) {
val result = authService.refreshToken(refresh)
result.fold(
onSuccess = { tokenResponse ->
tokenManager.saveTokens(
tokenResponse.access_token,
tokenResponse.refresh_token,
tokenManager.getUserId() ?: ""
)
BearerTokens(
tokenResponse.access_token,
tokenResponse.refresh_token
)
},
onFailure = {
tokenManager.clearTokens()
tokenManager.notifyTokenRefreshFailed()
null
}
)
} else {
tokenManager.clearTokens()
tokenManager.notifyTokenRefreshFailed()
null
}
}
sendWithoutRequest { true }
}
}
}
}

View File

@@ -45,6 +45,7 @@ ktor-client-websockets = { group = "io.ktor", name = "ktor-client-websockets", v
ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" }
ktor-client-logging = { group = "io.ktor", name = "ktor-client-logging", version.ref = "ktor" }
ktor-client-auth = { group = "io.ktor", name = "ktor-client-auth", version.ref = "ktor" }
# Room
androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }