Skip to content
Server Plugin

OAuth

必需的依赖项io.ktor:ktor-server-auth

代码示例 auth-oauth-google

原生服务器
Ktor supports Kotlin/Native and allows you to run a server without an additional runtime or virtual machine.
支持:✅

OAuth 是一种用于访问委托的开放标准。OAuth 可用于通过 Google、Facebook、Twitter 等外部提供程序来授权应用程序的用户。

oauth 提供程序支持授权码流。您可以在一个地方配置 OAuth 参数,Ktor 将自动使用必要的参数向指定的授权服务器发起请求。

您可以在 Ktor Server 中的身份验证与授权章节中获取有关 Ktor 身份验证与授权的一般信息。

添加依赖项

要使用 OAuth,您需要在构建脚本中包含 ktor-server-auth 构件:

Kotlin
Groovy
XML

安装 Sessions 插件

为了避免客户端每次尝试访问受保护资源时都请求授权,您可以在授权成功后将访问令牌存储在会话中。 然后,您可以在受保护路由的处理程序中从当前会话中检索访问令牌,并使用它来请求资源。

kotlin
import io.ktor.server.sessions.*

fun Application.main(httpClient: HttpClient = applicationHttpClient) {
    install(Sessions) {
        cookie<UserSession>("user_session")
    }
}

@Serializable
data class UserSession(val state: String, val token: String)

OAuth 授权流

Ktor 应用程序中的 OAuth 授权流可能如下所示:

  1. 用户在 Ktor 应用程序中打开登录页面。

  2. Ktor 自动重定向到特定提供程序的授权页面,并传递必要的参数

    • 客户端 ID:用于访问所选提供程序的 API。
    • 回调或重定向 URL:指定授权完成后将打开的 Ktor 应用程序页面。
    • 作用域(Scopes):Ktor 应用程序所需的第三方资源权限范围。
    • 授权类型(grant type):用于获取访问令牌(授权码)。
    • state 参数:用于缓解 CSRF 攻击并重定向用户。
    • 特定提供程序的其他可选参数。
  3. 授权页面会显示一个同意屏幕,列出 Ktor 应用程序所需的权限级别。这些权限取决于步骤 2:配置 OAuth 提供程序中所配置的作用域。

  4. 如果用户批准了请求的权限,授权服务器将重定向回指定的重定向 URL 并发送授权码。

  5. Ktor 会向指定的访问令牌 URL 再次发起自动请求,包含以下参数:

    • 授权码。
    • 客户端 ID 和客户端密钥。

    授权服务器通过返回访问令牌进行响应。

  6. 客户端随后可以使用此令牌向所选提供程序的所需服务发起请求。在大多数情况下,令牌会使用 Bearer 架构在 Authorization 标头中发送。

  7. 服务验证令牌,使用其作用域进行授权,并返回请求的数据。

安装 OAuth

要安装 oauth 身份验证提供程序,请在 install 块内调用 oauth 函数。此外,您可以指定提供程序名称。 例如,安装名为 "auth-oauth-google" 的 oauth 提供程序如下所示:

kotlin
import io.ktor.server.application.*
import io.ktor.server.auth.*

fun Application.main(httpClient: HttpClient = applicationHttpClient) {
    install(Authentication) {
        oauth("auth-oauth-google") {
            // 配置 oauth 身份验证
            urlProvider = { "http://localhost:8080/callback" }
            client = httpClient
        }
    }
}

配置 OAuth

本节演示如何配置 oauth 提供程序,以便通过 Google 授权应用程序的用户。 如需完整的可运行示例,请参阅 auth-oauth-google

前提条件:创建授权凭据

要访问 Google API,您需要在 Google Cloud Console 中创建授权凭据。

  1. 打开 Google Cloud Console 中的 Credentials(凭据)页面。

  2. 点击 CREATE CREDENTIALS(创建凭据)并选择 OAuth client ID(OAuth 客户端 ID)。

  3. 从下拉列表中选择 Web application(Web 应用程序)。

  4. 指定以下设置:

    • Authorised JavaScript origins(已获授权的 JavaScript 来源):http://localhost:8080
    • Authorised redirect URIs(已获授权的重定向 URI):http://localhost:8080/callback。 在 Ktor 中,urlProvider 属性用于指定授权完成后将打开的重定向路由。
  5. 点击 CREATE(创建)。

  6. 在弹出的对话框中,复制创建的客户端 ID 和客户端密钥,这些将用于配置 oauth 提供程序。

步骤 1:创建 HTTP 客户端

在配置 oauth 提供程序之前,您需要创建 HttpClient,服务器将使用它向 OAuth 服务器发起请求。需要带有 JSON 序列化程序的 ContentNegotiation 客户端插件,以便在请求 API 后对收到的 JSON 数据进行反序列化。

kotlin
val applicationHttpClient = HttpClient(CIO) {
    install(ContentNegotiation) {
        json(Json {
            ignoreUnknownKeys = true
        })
    }
}

客户端实例被传递到 main 模块函数,以便能够在服务器测试中创建单独的客户端实例。

kotlin
fun Application.main(httpClient: HttpClient = applicationHttpClient) {
}

步骤 2:配置 OAuth 提供程序

下面的代码片段显示了如何创建并配置名为 auth-oauth-googleoauth 提供程序。 对于具有固定 OAuth 设置的提供程序,请使用 settings 属性。

kotlin
val redirects = ConcurrentMap<String, String>()
install(Authentication) {
    oauth("auth-oauth-google") {
        // 配置 oauth 身份验证
        urlProvider = { "http://localhost:8080/callback" }
        settings = OAuthServerSettings.OAuth2ServerSettings(
                name = "google",
                authorizeUrl = "https://accounts.google.com/o/oauth2/auth",
                accessTokenUrl = "https://accounts.google.com/o/oauth2/token",
                requestMethod = HttpMethod.Post,
                clientId = System.getenv("GOOGLE_CLIENT_ID").orEmpty(),
                clientSecret = System.getenv("GOOGLE_CLIENT_SECRET").orEmpty(),
                defaultScopes = listOf("https://www.googleapis.com/auth/userinfo.profile"),
                extraAuthParameters = listOf("access_type" to "offline"),
                onStateCreated = { call, state ->
                    // 保存带有重定向 url 值的新状态
                    call.request.queryParameters["redirectUrl"]?.let {
                        redirects[state] = it
                    }
                }
            )
        fallback = { cause ->
            if (cause is OAuth2RedirectError) {
                respondRedirect("/login-after-fallback")
            } else {
                respond(HttpStatusCode.Forbidden, cause.message)
            }
        }
        client = httpClient
    }
}
  • urlProvider 指定了授权完成后将调用的重定向路由

    请确保已将此路由添加到 Authorised redirect URIs 列表中。

  • settings 属性为提供程序指定静态 OAuth 设置。这些设置由 OAuthServerSettings 类表示,并允许 Ktor 自动向 OAuth 服务器发起请求。对于静态提供程序配置,相比 providerLookup 更推荐使用 settings,因为它还允许 Ktor 为生成的 OpenAPI 规范推断元数据。
  • fallback 属性通过响应重定向或自定义响应来处理 OAuth 流错误。
  • client 属性指定了 Ktor 用来向 OAuth 服务器发起请求的 HttpClient

providerLookup 属性仍然受支持,用于为特定调用动态解析 OAuth 设置。当提供程序配置依赖于请求数据(如租户特定的凭据或端点)时,请使用它。

步骤 3:添加登录路由

配置 oauth 提供程序后,您需要在接受 oauth 提供程序名称的 authenticate 函数内部创建一个受保护的登录路由。当 Ktor 收到对该路由的请求时,它将自动重定向到 settings 中定义的 authorizeUrl

kotlin
routing {
    authenticate("auth-oauth-google") {
        get("/login") {
            // 自动重定向到 'authorizeUrl'
        }
    }
}

用户将看到授权页面以及 Ktor 应用程序所需的权限级别。这些权限取决于 settings 中指定的 defaultScopes

步骤 4:添加重定向路由

除了登录路由外,您还需要为 urlProvider 创建重定向路由,如步骤 2:配置 OAuth 提供程序中所述。

在此路由内部,您可以使用 call.principal 函数检索 OAuthAccessTokenResponse 对象。OAuthAccessTokenResponse 允许您访问令牌以及由 OAuth 服务器返回的其他参数。

kotlin
    routing {
        authenticate("auth-oauth-google") {
            get("/login") {
                // 自动重定向到 'authorizeUrl'
            }

            get("/callback") {
                val currentPrincipal: OAuthAccessTokenResponse.OAuth2? = call.principal()
                // 如果在授权前找不到该 url,则重定向回主页
                currentPrincipal?.let { principal ->
                    principal.state?.let { state ->
                        call.sessions.set(UserSession(state, principal.accessToken))
                        redirects.remove(state)?.let { redirect ->
                            call.respondRedirect(redirect)
                            return@get
                        }
                    }
                }
                call.respondRedirect("/home")
            }
        }
    }

在此示例中,接收令牌后执行了以下操作:

  • 令牌被保存在 会话 中,其内容可以在其他路由中访问。
  • 用户被重定向到下一个发起 Google API 请求的路由。
  • 如果未找到请求的路由,用户将被重定向到 /home 路由。

步骤 5:发起 API 请求

重定向路由中收到令牌并将其保存到会话后,您可以使用此令牌向外部 API 发起请求。下面的代码片段显示了如何使用 HttpClient 发起此类请求,并通过在 Authorization 标头中发送此令牌来获取用户信息。

创建一个名为 getPersonalGreeting 的新函数,它将发起请求并返回响应体:

kotlin
private suspend fun getPersonalGreeting(
    httpClient: HttpClient,
    userSession: UserSession
): UserInfo = httpClient.get("https://www.googleapis.com/oauth2/v2/userinfo") {
    headers {
        append(HttpHeaders.Authorization, "Bearer ${userSession.token}")
    }
}.body()

然后,您可以在 get 路由中调用该函数来检索用户信息:

kotlin
get("/{path}") {
    val userSession: UserSession? = getSession(call)
    if (userSession != null) {
        val userInfo: UserInfo = getPersonalGreeting(httpClient, userSession)
        call.respondText("Hello, ${userInfo.name}!")
    }
}

如需完整的可运行示例,请参阅 auth-oauth-google