Skip to content
Server Plugin

OAuth

必要相依性io.ktor:ktor-server-auth

程式碼範例 auth-oauth-google

原生伺服器
Ktor 支援 Kotlin/Native,允許您在沒有額外執行階段或虛擬機的情況下執行伺服器。
支援:✅

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 自動重新導向至特定提供者的授權頁面,並傳遞必要的 參數

    • 用於存取所選提供者 API 的用戶端 ID。
    • 回呼或重新導向 URL,指定授權完成後將開啟的 Ktor 應用程式頁面。
    • Ktor 應用程式所需的第三方資源範圍 (Scopes)。
    • 用於獲取存取權杖的授權類型(授權碼)。
    • 用於緩解 CSRF 攻擊和重新導向使用者的 state 參數。
    • 特定提供者的選用參數。
  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") {
            // Configure oauth authentication
            urlProvider = { "http://localhost:8080/callback" }
            client = httpClient
        }
    }
}

設定 OAuth

本節演示如何設定 oauth 提供者,以便使用 Google 授權您的應用程式使用者。 如需完整的可執行範例,請參閱 auth-oauth-google

先決條件:建立授權憑據

要存取 Google API,您需要在 Google Cloud 控制台中建立授權憑據。

  1. 在 Google Cloud 控制台中開啟 憑據 頁面。

  2. 點擊 建立憑據 並選擇 OAuth 用戶端 ID

  3. 從下拉式功能表中選擇 網頁應用程式

  4. 指定以下設定:

    • 已授權的 JavaScript 來源http://localhost:8080
    • 已授權的重新導向 URIhttp://localhost:8080/callback。 在 Ktor 中,urlProvider 屬性用於指定授權完成時將開啟的重新導向路由。
  5. 點擊 建立

  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") {
        // Configure oauth authentication
        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 ->
                    //saves new state with redirect url value
                    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 指定了授權完成時將呼叫的 重新導向路由

    請確保此路由已新增至 已授權的重新導向 URI 清單中。

  • settings 屬性指定了提供者的靜態 OAuth 設定。這些設定由 OAuthServerSettings 類別表示,並允許 Ktor 向 OAuth 伺服器發送自動請求。對於靜態提供者設定,建議優先選用 settings 而非 providerLookup,因為這也能讓 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") {
            // Redirects to 'authorizeUrl' automatically
        }
    }
}

使用者將看到授權頁面,列出 Ktor 應用程式所需的權限級別。這些權限取決於 settings 中指定的 defaultScopes

步驟 4:新增重新導向路由

除了登入路由之外,您還需要為 urlProvider 建立重新導向路由,如 步驟 2:設定 OAuth 提供者 中所述。

在此路由內部,您可以使用 call.principal 函式檢索 OAuthAccessTokenResponse 物件。OAuthAccessTokenResponse 允許您存取由 OAuth 伺服器傳回的權杖與其他參數。

kotlin
    routing {
        authenticate("auth-oauth-google") {
            get("/login") {
                // Redirects to 'authorizeUrl' automatically
            }

            get("/callback") {
                val currentPrincipal: OAuthAccessTokenResponse.OAuth2? = call.principal()
                // redirects home if the url is not found before authorization
                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