Ktor Server 中的身份驗證與授權
必要的相依性:io.ktor:ktor-server-auth
Ktor 提供 Authentication 外掛程式來處理身份驗證與授權。典型使用案例包括登入使用者、授予對特定資源的存取權限,以及在各方之間安全地傳輸資訊。您也可以將 Authentication 與 Sessions(工作階段)搭配使用,以在請求之間保留使用者的資訊。
在用戶端,Ktor 提供了 Authentication 外掛程式來處理身份驗證與授權。
支援的身份驗證類型
Ktor 支援以下身份驗證與授權配置:
HTTP 身份驗證
HTTP 為存取控制和身份驗證提供了一個通用架構。在 Ktor 中,您可以使用以下 HTTP 身份驗證機制:
- Basic - 使用
Base64編碼來提供使用者名稱和密碼。若不與 HTTPS 結合使用,通常不建議使用。 - Digest - 一種透過對使用者名稱和密碼套用雜湊函式,以加密形式傳輸使用者憑據的身份驗證方法。
- Bearer - 一種涉及稱為 bearer token(持票者權杖)的安全權杖身份驗證機制。 Bearer 身份驗證機制通常作為 OAuth 或 JWT 的一部分使用,但您也可以為授權 bearer token 提供自訂邏輯。
- API Key - 一種簡單的身份驗證方法,用戶端在標頭中傳遞秘密金鑰。
表單身份驗證
表單身份驗證使用 Web 表單來收集憑據資訊並驗證使用者身份。
JSON Web Tokens (JWT)
JSON Web Token 是一種開放標準,用於將資訊作為 JSON 物件在各方之間安全地傳輸。您可以使用 JSON Web Token 進行授權:當使用者登入後,每個請求都將包含一個權杖,允許使用者存取該權杖所許可的資源。在 Ktor 中,您可以使用 jwt 身份驗證來驗證權杖並驗證其中包含的宣告(claims)。
LDAP
LDAP 是一種用於目錄服務身份驗證的開放式跨平台協定。Ktor 提供 ldapAuthenticate 函式,用於根據指定的 LDAP 伺服器驗證使用者憑據。
OAuth
OAuth 是一種用於保護 API 存取權限的開放標準。Ktor 中的 oauth 提供者允許您使用外部提供者(如 Google、Facebook、Twitter 等)實作身份驗證。
Session
Sessions(工作階段)提供了一種在不同 HTTP 請求之間持久化資料的機制。典型使用案例包括儲存已登入使用者的 ID、購物車內容,或在用戶端保留使用者偏好。在 Ktor 中,已經擁有相關聯工作階段的使用者可以使用 session 提供者進行驗證。請從 Ktor Server 中的 Session 身份驗證了解如何操作。
自訂
Ktor 提供兩種自訂身份驗證與授權行為的方式:
- 使用自訂身份驗證提供者。
- 使用自訂外掛程式來實作授權邏輯。例如,您可以使用
AuthenticationCheckedhook 來驗證存取權限。如需更多資訊,請參閱 custom-plugin-authorization 範例。
新增相依性
要使用 Authentication,您需要在組建指令碼中包含 ktor-server-auth 構件:
請注意,某些身份驗證提供者(例如 JWT 和 LDAP)需要額外的構件。
安裝 Authentication
若要將 Authentication 外掛程式安裝到應用程式,請將其傳遞給指定
install 函式。 以下程式碼片段顯示了如何安裝 Authentication ... - ... 在
embeddedServer函式呼叫內部。 - ... 在顯式定義的
module內部,該模組是Application類別的擴充函式。
配置 Authentication
安裝 Authentication 後,您可以按照以下步驟配置並使用 Authentication:
步驟 1:選擇身份驗證提供者
若要使用特定的身份驗證提供者,例如 basic、digest 或 form, 您需要在 install 區塊內呼叫相應的函式。例如,要使用 basic 身份驗證, 請呼叫 .basic() 函式:
import io.ktor.server.application.*
import io.ktor.server.auth.*
// ...
install(Authentication) {
basic {
// 配置 basic 身份驗證
}
}在此函式內,您可以配置該提供者特定的設定。
如果內建提供者不符合您的需求,您可以實作自訂身份驗證提供者。
步驟 2:指定提供者名稱
使用特定提供者的函式可以選擇指定提供者名稱。以下程式碼範例分別以 "auth-basic" 和 "auth-form" 名稱安裝了 basic 和 form 提供者:
install(Authentication) {
basic("auth-basic") {
// 配置 basic 身份驗證
}
form("auth-form") {
// 配置 form 身份驗證
}
// ...
}這些名稱稍後可用於使用不同提供者驗證不同路由。
請注意,提供者名稱應該是唯一的,且您只能定義一個不帶名稱的提供者。
步驟 3:配置提供者
每個提供者類型都有自己的配置。例如, BasicAuthenticationProvider.Config 類別為 .basic() 函式提供選項。此類別中的關鍵函式是 validate(),負責驗證使用者名稱和密碼。以下程式碼範例展示了其用法:
install(Authentication) {
basic("auth-basic") {
realm = "Access to the '/' path"
validate { credentials ->
if (credentials.name == "jetbrains" && credentials.password == "foobar") {
UserIdPrincipal(credentials.name)
} else {
null
}
}
}
}為了理解 validate() 函式的運作方式,我們需要引入兩個術語:
- 委託主體 (principal) 是一個可以被驗證的實體:使用者、電腦、服務等。在 Ktor 中,各種身份驗證提供者可能會使用不同的委託主體。例如,
basic、digest和form提供者驗證UserIdPrincipal,而jwt提供者驗證JWTPrincipal。您也可以建立自訂委託主體。這在以下情況可能很有用:
- 將憑據映射到自訂委託主體,可讓您在路由處理常式中獲取有關已驗證委託主體的額外資訊。
- 如果您使用 Session 身份驗證,委託主體可能是一個儲存工作階段資料的資料類別。
- 憑據 (credential) 是伺服器用來驗證委託主體的一組屬性:使用者/密碼配對、API 金鑰等。例如,
basic和form提供者使用UserPasswordCredential來驗證使用者名稱和密碼,而jwt則驗證JWTCredential。
因此,validate() 函式會檢查指定的憑據,並在驗證成功時傳回委託主體 Any,若驗證失敗則傳回 null。
若要根據特定標準跳過身份驗證,請使用
skipWhen()。例如,如果 工作階段 已存在,您可以跳過basic身份驗證:kotlinbasic { skipWhen { call -> call.sessions.get<UserSession>() != null } }
步驟 4:保護特定資源
最後一步是保護應用程式中的特定資源。您可以使用 authenticate() 函式來達成。此函式接受兩個選用參數:
用於驗證巢狀路由的提供者名稱。 以下程式碼片段使用名為 auth-basic 的提供者來保護
/login和/orders路由:kotlinrouting { authenticate("auth-basic") { get("/login") { // ... } get("/orders") { // ... } } get("/") { // ... } }用於解析巢狀身份驗證提供者的策略。 此策略由
AuthenticationStrategy列舉值表示。例如,用戶端應為所有註冊為
AuthenticationStrategy.Required策略的提供者提供身份驗證資料。 在下面的程式碼片段中,只有通過 Session 身份驗證的使用者才能嘗試使用 basic 身份驗證存取/admin路由:kotlinrouting { authenticate("auth-session", strategy = AuthenticationStrategy.Required) { get("/hello") { // ... } authenticate("auth-basic", strategy = AuthenticationStrategy.Required) { get("/admin") { // ... } } } }
如需完整範例,請參閱 auth-form-session-nested。
步驟 5:在路由處理常式中獲取委託主體
身份驗證成功後,您可以在路由處理常式中使用 call.principal() 函式擷取已驗證的委託主體。此函式接受已配置身份驗證提供者傳回的特定委託主體類型。在以下範例中,call.principal() 用於獲取 UserIdPrincipal 並取得已驗證使用者的名稱。
routing {
authenticate("auth-basic") {
get("/") {
call.respondText("Hello, ${call.principal<UserIdPrincipal>()?.name}!")
}
}
}如果您使用 Session 身份驗證,委託主體可能是一個儲存工作階段資料的資料類別。因此,您需要將此資料類別傳遞給 call.principal():
authenticate("auth-session") {
get("/hello") {
val userSession = call.principal<UserSession>()
}
}在巢狀身份驗證提供者的情況下,您可以將提供者名稱傳遞給 call.principal(),以獲取所需提供者的委託主體。
在下面的範例中,傳遞了 "auth-session" 值來獲取最頂層 session 提供者的委託主體:
authenticate("auth-session", strategy = AuthenticationStrategy.Required) {
authenticate("auth-basic", strategy = AuthenticationStrategy.Required) {
get("/admin") {
val userSession = call.principal<UserSession>("auth-session")
}
}
}自訂身份驗證提供者
當內建提供者不符合您的需求時,請使用 provider() 函式實作自訂身份驗證邏輯:
provider("custom") {
authenticate { context ->
val exampleHeader = context.call.request.headers["Example-Header"]
if (exampleHeader == null) {
val cause = AuthenticationFailedCause.Error("No example header found")
context.challenge(key = this, cause) { challenge, call ->
call.respondText("Challenge")
challenge.complete()
}
}
}
}在上述範例中,provider("custom") 函式註冊了一個具名的身份驗證提供者,稍後可以透過 authenticate("custom") 套用於路由。
在提供者內部,authenticate {} 區塊會針對每個傳入請求執行,並讓您透過內容物件完全控制身份驗證程序。這包括存取當前呼叫 (context.call),以及檢查標頭、參數或其他請求資料的能力。
您可以使用 DynamicProviderConfig 類別提供的選項來配置額外的提供者行為。
