會話
所需依賴項: io.ktor:ktor-server-sessions
程式碼範例: session-cookie-client, session-cookie-server, session-header-server
The Sessions 外掛提供了一種在不同 HTTP 請求之間持久化資料的機制。典型應用場景包括儲存已登入使用者的 ID、購物籃的內容或在客戶端保留使用者偏好設定。在 Ktor 中,您可以透過使用 Cookie 或自訂標頭來實作會話,選擇將會話資料儲存在伺服器上還是傳遞給客戶端,以及簽署和加密會話資料等等。
在本主題中,我們將探討如何安裝 Sessions
外掛、配置它以及在路由處理程式內部存取會話資料。
新增依賴項
為了啟用會話支援,您需要在建置指令碼中包含 ktor-server-sessions
artifact:
安裝 Sessions
要將 Sessions
外掛安裝到應用程式中, 請將其傳遞給指定
install
函數。 下面的程式碼片段展示了如何安裝 Sessions
... - ... 在
embeddedServer
函數呼叫內部。 - ... 在明確定義的
module
內部,它是一個Application
類別的擴充函數。
Sessions
外掛也可以安裝到特定路由。 如果您需要針對不同的應用程式資源使用不同的 Sessions
配置,這將會很有用。
會話配置概覽
要配置 Sessions
外掛,您需要執行以下步驟:
選擇如何在伺服器和客戶端之間傳遞資料:使用 Cookie 或自訂標頭。Cookie 更適合純 HTML 應用程式,而自訂標頭則用於 API。
選擇在哪裡儲存會話有效負載:在客戶端或伺服器端。您可以透過 Cookie/標頭值將序列化的會話資料傳遞給客戶端,或者將有效負載儲存在伺服器上,僅傳遞會話識別碼。
如果您想將會話有效負載儲存在伺服器上,您可以*選擇如何儲存它*:在伺服器記憶體中或在一個資料夾中。您也可以實作自訂儲存以保留會話資料。
保護會話資料:為了保護傳遞給客戶端的敏感會話資料,您需要簽署並加密會話的有效負載。
配置 Sessions
後,您可以在路由處理程式內部取得並設定會話資料。
建立資料類別
在配置會話之前,您需要為儲存會話資料建立一個資料類別。 例如,下面的 UserSession
類別將用於儲存會話 ID 和頁面瀏覽次數:
@Serializable
data class UserSession(val id: String, val count: Int)
如果您打算使用多個會話,您需要建立多個資料類別。
傳遞會話資料:Cookie vs 標頭
Cookie
要使用 Cookie 傳遞會話資料,請在 install(Sessions)
區塊內呼叫帶有指定名稱和資料類別的 cookie
函數:
install(Sessions) {
cookie<UserSession>("user_session")
}
在上面的範例中,會話資料將使用添加到 Set-Cookie
標頭中的 user_session
屬性傳遞給客戶端。您可以透過在 cookie
區塊內部傳遞其他 Cookie 屬性來配置它們。例如,下面的程式碼片段展示了如何指定 Cookie 的路徑和過期時間:
install(Sessions) {
cookie<UserSession>("user_session") {
cookie.path = "/"
cookie.maxAgeInSeconds = 10
}
}
如果所需的屬性沒有明確公開,請使用 extensions
屬性。例如,您可以透過以下方式傳遞 SameSite
屬性:
install(Sessions) {
cookie<UserSession>("user_session") {
cookie.extensions["SameSite"] = "lax"
}
}
要了解更多可用的配置設定,請參閱 CookieConfiguration。
在將您的應用程式部署到生產環境之前,請確保
secure
屬性設定為true
。這將啟用僅透過安全連線傳輸 Cookie,並保護會話資料免受 HTTPS 降級攻擊。
標頭
要使用自訂標頭傳遞會話資料,請在 install(Sessions)
區塊內呼叫帶有指定名稱和資料類別的 header
函數:
install(Sessions) {
header<CartSession>("cart_session")
}
在上面的範例中,會話資料將使用 cart_session
自訂標頭傳遞給客戶端。 在客戶端,您需要將此標頭附加到每個請求以取得會話資料。
如果您使用 CORS 外掛來處理跨來源請求,請將您的自訂標頭按如下方式添加到
CORS
配置中:kotlininstall(CORS) { allowHeader("cart_session") exposeHeader("cart_session") }
儲存會話有效負載:客戶端 vs 伺服器
在 Ktor 中,您可以透過兩種方式管理會話資料:
在客戶端和伺服器之間傳遞會話資料。
如果您只將會話名稱傳遞給 Cookie 或標頭函數,則會話資料將在客戶端和伺服器之間傳遞。在這種情況下,您需要簽署並加密會話的有效負載,以保護傳遞給客戶端的敏感會話資料。
在伺服器上儲存會話資料,並僅在客戶端和伺服器之間傳遞會話 ID。
在這種情況下,您可以選擇在哪裡儲存有效負載在伺服器上。例如,您可以將會話資料儲存在記憶體中、指定資料夾中,或者您可以實作您自己的自訂儲存。
在伺服器上儲存會話有效負載
Ktor 允許您在伺服器上儲存會話資料,並僅在伺服器和客戶端之間傳遞會話 ID。在這種情況下,您可以選擇在哪裡保留伺服器上的有效負載。
記憶體儲存
SessionStorageMemory 啟用在記憶體中儲存會話內容。此儲存會在伺服器運行時保留資料,一旦伺服器停止就會丟棄資訊。例如,您可以如下在伺服器記憶體中儲存 Cookie:
cookie<CartSession>("cart_session", SessionStorageMemory()) {
}
您可以在此處找到完整範例:session-cookie-server。
請注意,
SessionStorageMemory
僅用於開發。
目錄儲存
directorySessionStorage 可用於將會話資料儲存在指定目錄下的檔案中。例如,要在 build/.sessions
目錄下儲存會話資料,請以這種方式建立 directorySessionStorage
:
header<CartSession>("cart_session", directorySessionStorage(File("build/.sessions"))) {
}
您可以在此處找到完整範例:session-header-server。
自訂儲存
Ktor 提供了 SessionStorage 介面,允許您實作自訂儲存。
interface SessionStorage {
suspend fun invalidate(id: String)
suspend fun write(id: String, value: String)
suspend fun read(id: String): String
}
所有三個函數都是 suspending 函數。您可以使用 SessionStorageMemory 作為參考。
保護會話資料
簽署會話資料
簽署會話資料可以防止修改會話內容,但允許使用者查看此內容。 要簽署會話,請將簽名金鑰傳遞給 SessionTransportTransformerMessageAuthentication
建構函數,並將此實例傳遞給 transform
函數:
install(Sessions) {
val secretSignKey = hex("6819b57a326945c1968f45236589")
cookie<CartSession>("cart_session", SessionStorageMemory()) {
cookie.path = "/"
transform(SessionTransportTransformerMessageAuthentication(secretSignKey))
}
}
SessionTransportTransformerMessageAuthentication
使用 HmacSHA256
作為預設身份驗證演算法,該演算法可以更改。
簽署並加密會話資料
簽署並加密會話資料可以防止讀取和修改會話內容。 要簽署並加密會話,請將簽名/加密金鑰傳遞給 SessionTransportTransformerEncrypt
建構函數,並將此實例傳遞給 transform
函數:
install(Sessions) {
val secretEncryptKey = hex("00112233445566778899aabbccddeeff")
val secretSignKey = hex("6819b57a326945c1968f45236589")
cookie<UserSession>("user_session") {
cookie.path = "/"
cookie.maxAgeInSeconds = 10
transform(SessionTransportTransformerEncrypt(secretEncryptKey, secretSignKey))
}
}
請注意,Ktor
3.0.0
版本中加密方法已更新。如果您從早期版本遷移,請在SessionTransportTransformerEncrypt
的建構函數中使用backwardCompatibleRead
屬性,以確保與現有會話的相容性。
預設情況下,SessionTransportTransformerEncrypt
使用 AES
和 HmacSHA256
演算法,這些演算法可以更改。
請注意,簽名/加密金鑰不應在程式碼中指定。您可以在配置檔案中使用自訂群組來儲存簽名/加密金鑰,並使用環境變數初始化它們。
取得並設定會話內容
要為特定路由設定會話內容,請使用 call.sessions
屬性。set
方法允許您建立新的會話實例:
get("/login") {
call.sessions.set(UserSession(id = "123abc", count = 0))
call.respondRedirect("/user")
}
要取得會話內容,您可以呼叫 get
,將其中一個已註冊的會話類型作為類型參數:
get("/user") {
val userSession = call.sessions.get<UserSession>()
if (userSession != null) {
}
要修改會話,例如遞增計數器,您需要呼叫資料類別的 copy
方法:
get("/user") {
val userSession = call.sessions.get<UserSession>()
if (userSession != null) {
call.sessions.set(userSession.copy(count = userSession.count + 1))
call.respondText("Session ID is ${userSession.id}. Reload count is ${userSession.count}.")
} else {
call.respondText("Session doesn't exist or is expired.")
}
}
當您需要因任何原因清除會話時(例如,當使用者登出時),請呼叫 clear
函數:
get("/logout") {
call.sessions.clear<UserSession>()
call.respondRedirect("/user")
}
您可以在此處找到完整範例:session-cookie-client。
延遲會話擷取
預設情況下,Ktor 會嘗試為每個包含會話的請求從儲存中讀取會話,無論路由是否實際需要它。這種行為可能會導致不必要的開銷 — 特別是在使用自訂會話儲存的應用程式中。
您可以透過啟用 io.ktor.server.sessions.deferred
系統屬性來延遲會話載入:
System.setProperty("io.ktor.server.sessions.deferred", "true")
範例
下面的可執行範例演示了如何使用 Sessions
外掛:
- session-cookie-client 展示了如何使用 Cookie 將簽署並加密的會話有效負載傳遞給客戶端。
- session-cookie-server 展示了如何將會話有效負載保留在伺服器記憶體中,並使用 Cookie 將簽署的會話 ID 傳遞給客戶端。
- session-header-server 展示了如何在伺服器上的目錄儲存中保留會話有效負載,並使用自訂標頭將簽署的會話 ID 傳遞給客戶端。