适用于 Kotlin 的 OAuth 客户端:通过 ktor-client 和 kotlinx-serialization 与提供商集成
OAuth 2.0 是一种标准的授权协议,允许应用程序在不传递用户凭据的情况下,获取对用户资源的有限访问权限。在 Kotlin 生态系统中,得益于与 JVM 的兼容性和多平台特性,可以使用现代库高效地实现 OAuth 客户端。在本文中,我们将探讨如何使用 ktor-client 进行 HTTP 请求,以及使用 kotlinx-serialization 处理 JSON,在 Kotlin 中创建 OAuth 客户端。
OAuth 客户端的主要组件
- HTTP 客户端 —
ktor-client,用于向授权服务器和资源服务器发送请求。 - 序列化 —
kotlinx-serialization,用于将响应解析为 data 类。 - 令牌存储 — 内存存储或持久化存储(SharedPreferences、文件)。
- 状态管理器 — 处理授权码、刷新令牌、CSRF 保护。
添加依赖项
要在 Kotlin 中使用 OAuth,请在 build.gradle.kts(或 build.gradle)中添加以下依赖项:
// build.gradle.ktsdependencies { implementation("io.ktor:ktor-client-core:2.3.12") implementation("io.ktor:ktor-client-cio:2.3.12") // 引擎 implementation("io.ktor:ktor-client-content-negotiation:2.3.12") implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.12") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")}OAuth 客户端的基本架构
我们将逻辑分为三层:
- 模型(DTO) — 用于请求/响应的 data 类。
- 服务 — 管理授权流程的类。
- 客户端 — 对 ktor-client 的封装,提供不同授权类型的方法。
数据模型
import kotlinx.serialization.SerialNameimport kotlinx.serialization.Serializable
@Serializabledata class TokenResponse( @SerialName("access_token") val accessToken: String, @SerialName("token_type") val tokenType: String = "Bearer", @SerialName("expires_in") val expiresIn: Long? = null, @SerialName("refresh_token") val refreshToken: String? = null, @SerialName("scope") val scope: String? = null, @SerialName("id_token") val idToken: String? = null // 用于 OpenID Connect)
@Serializabledata class OAuthError( val error: String, @SerialName("error_description") val errorDescription: String? = null)OAuth2 客户端
import io.ktor.client.*import io.ktor.client.call.*import io.ktor.client.plugins.contentnegotiation.*import io.ktor.client.request.*import io.ktor.client.statement.*import io.ktor.http.*import io.ktor.serialization.kotlinx.json.*import kotlinx.serialization.json.Json
class OAuth2Client( private val clientId: String, private val clientSecret: String, private val redirectUri: String, private val tokenEndpoint: String, private val authEndpoint: String) { private val httpClient = HttpClient(CIO) { install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true isLenient = true }) } }
// 生成重定向到授权页面的 URL fun getAuthorizationUrl(state: String, scope: String = "openid profile email"): String { return "$authEndpoint?response_type=code" + "&client_id=$clientId" + "&redirect_uri=${java.net.URLEncoder.encode(redirectUri, "UTF-8")}" + "&scope=${java.net.URLEncoder.encode(scope, "UTF-8")}