feat: 添加 Ktor 客户端认证插件以增强 API 安全性
- 在 build.gradle.kts 中新增 Ktor 客户端认证依赖。 - 更新 ApiService 类,使用 Ktor 内置的 Auth 插件实现 Bearer token 自动管理和刷新逻辑,提升 API 调用的安全性和稳定性。
This commit is contained in:
@@ -137,6 +137,7 @@ dependencies {
|
|||||||
implementation(libs.ktor.client.content.negotiation)
|
implementation(libs.ktor.client.content.negotiation)
|
||||||
implementation(libs.ktor.serialization.kotlinx.json)
|
implementation(libs.ktor.serialization.kotlinx.json)
|
||||||
implementation(libs.ktor.client.logging)
|
implementation(libs.ktor.client.logging)
|
||||||
|
implementation(libs.ktor.client.auth)
|
||||||
|
|
||||||
// Room
|
// Room
|
||||||
implementation(libs.androidx.room.runtime)
|
implementation(libs.androidx.room.runtime)
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
package com.huaga.life_echo.network
|
package com.huaga.life_echo.network
|
||||||
|
|
||||||
import com.huaga.life_echo.data.auth.TokenManager
|
import com.huaga.life_echo.data.auth.TokenManager
|
||||||
import com.huaga.life_echo.network.interceptors.AuthInterceptorPlugin
|
|
||||||
import com.huaga.life_echo.network.models.*
|
import com.huaga.life_echo.network.models.*
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.call.*
|
import io.ktor.client.call.*
|
||||||
import io.ktor.client.engine.android.*
|
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.contentnegotiation.*
|
||||||
import io.ktor.client.plugins.logging.*
|
import io.ktor.client.plugins.logging.*
|
||||||
import io.ktor.client.plugins.HttpTimeout
|
import io.ktor.client.plugins.HttpTimeout
|
||||||
@@ -19,8 +20,8 @@ import kotlinx.serialization.json.jsonObject
|
|||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
|
||||||
class ApiService(
|
class ApiService(
|
||||||
tokenManager: TokenManager? = null,
|
private val tokenManager: TokenManager? = null,
|
||||||
authService: AuthService? = null
|
private val authService: AuthService? = null
|
||||||
) {
|
) {
|
||||||
private val client = HttpClient(Android) {
|
private val client = HttpClient(Android) {
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
@@ -36,12 +37,49 @@ class ApiService(
|
|||||||
connectTimeoutMillis = 15_000 // 连接超时
|
connectTimeoutMillis = 15_000 // 连接超时
|
||||||
socketTimeoutMillis = 45_000 // 读写超时
|
socketTimeoutMillis = 45_000 // 读写超时
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果提供了tokenManager和authService,安装认证拦截器
|
// 使用Ktor内置Auth插件:自动添加Bearer token,401时自动刷新并重试
|
||||||
if (tokenManager != null && authService != null) {
|
if (tokenManager != null && authService != null) {
|
||||||
install(AuthInterceptorPlugin) {
|
install(Auth) {
|
||||||
this.tokenManager = tokenManager
|
bearer {
|
||||||
this.authService = authService
|
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 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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-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-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-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
|
# Room
|
||||||
androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
|
androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
|
||||||
|
|||||||
Reference in New Issue
Block a user