会话
所需依赖项: io.ktor:ktor-server-sessions
代码示例: session-cookie-client, session-cookie-server, session-header-server
Sessions
插件提供了一种在不同 HTTP 请求之间持久化数据的机制。典型用例包括存储已登录用户的 ID、购物车内容或在客户端保存用户偏好设置。在 Ktor 中,您可以使用 Cookie 或自定义头部(header)来实现会话,选择是在服务器端存储会话数据还是将其传递给客户端,对会话数据进行签名和加密等。
在本主题中,我们将探讨如何安装 Sessions
插件、配置它,以及在路由处理器内部访问会话数据。
添加依赖项
为了启用会话支持,您需要在构建脚本中包含 ktor-server-sessions
构件:
安装 Sessions
要将 `Sessions` 插件安装到应用程序中, 请在指定的
install
函数。 以下代码片段展示了如何安装 Sessions
... - ... 在
embeddedServer
函数调用内部。 - ... 在显式定义的
module
内部,module
是Application
类的一个扩展函数。
Sessions
插件也可以安装到特定的路由。 如果您需要为不同的应用程序资源使用不同的 Sessions
配置,这可能会很有用。
会话配置概述
要配置 Sessions
插件,您需要执行以下步骤:
创建数据类:在配置会话之前,您需要创建一个 data class 用于存储会话数据。
选择服务器和客户端之间的数据传递方式:使用 Cookie 或自定义头部。Cookie 更适合纯 HTML 应用程序,而自定义头部适用于 API。
选择会话负载的存储位置:在客户端或服务器端。您可以使用 Cookie/头部值将序列化的会话数据传递给客户端,或者将负载存储在服务器上,只传递会话标识符。
如果您想将会话负载存储在服务器上,您可以*选择如何存储它*:在服务器内存中或在一个文件夹中。您还可以实现自定义存储以保留会话数据。
保护会话数据:为了保护传递给客户端的敏感会话数据,您需要对会话负载进行签名和加密。
配置 Sessions
后,您可以在路由处理器内部获取和设置会话数据。
创建数据类
在配置会话之前,您需要创建一个 data class 用于存储会话数据。 例如,下面的 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 或 header 函数,会话数据将在客户端和服务器之间传递。在这种情况下,您需要对会话负载进行签名和加密,以保护传递给客户端的敏感会话数据。
将数据存储在服务器上,并且只在客户端和服务器之间传递会话 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
}
所有这三个函数都是挂起函数。您可以使用 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 传递给客户端。