Skip to content
Server Plugin

Sessions

所需依赖项io.ktor:ktor-server-sessions

代码示例session-cookie-clientsession-cookie-serversession-header-server

原生服务器
Ktor 支持 Kotlin/Native,允许您在没有额外运行时或虚拟机的情况下运行服务器。
支持:✅

Sessions 插件提供了一种在不同 HTTP 请求之间持久化数据的机制。典型的用例包括存储已登录用户的 ID、购物车的内容或在客户端上保留用户偏好。在 Ktor 中,您可以使用 Cookie 或自定义标头来实现会话,选择是在服务器上存储会话数据还是将其传递给客户端,对会话数据进行签名和加密等等。

在本主题中,我们将介绍如何安装 Sessions 插件、对其进行配置,以及如何在 路由处理程序 中访问会话数据。

添加依赖项

要启用对会话的支持,您需要在构建脚本中包含 ktor-server-sessions 构件:

Kotlin
Groovy
XML

安装 Sessions

要将 Sessions 插件安装到应用中, 请将其传递给指定

模块
模块允许您通过对路由进行分组来构建应用程序。
中的 install 函数。 下面的代码片段显示了如何安装 Sessions ...

  • ... 在 embeddedServer 函数调用中。
  • ... 在显式定义的 module 中,它是 Application 类的扩展函数。
kotlin
kotlin

Sessions 插件也可以被安装到特定路由。 如果您需要为不同的应用资源使用不同的 Sessions 配置,这可能会很有用。

会话配置概述

要配置 Sessions 插件,您需要执行以下步骤:

  1. 创建数据类:在配置会话之前,您需要创建一个数据类用于存储会话数据。

  2. 选择如何在服务器和客户端之间传递数据:使用 Cookie 或自定义标头。Cookie 更适合纯 HTML 应用,而自定义标头则旨在用于 API。

  3. 选择存储会话有效负载的位置:在客户端或服务器上。您可以使用 Cookie/标头值将序列化的会话数据传递给客户端,也可以将有效负载存储在服务器上并仅传递会话标识符。

    如果您想在服务器上存储会话有效负载,您可以 选择存储方式:在服务器内存中或文件夹中。您还可以实现自定义存储来保存会话数据。

  4. 保护会话数据:为了保护传递给客户端的敏感会话数据,您需要对会话的有效负载进行签名并加密。

配置好 Sessions 后,您可以在 路由处理程序获取并设置会话数据

创建数据类

在配置会话之前,您需要创建一个 数据类 用于存储会话数据。 例如,下面的 UserSession 类将用于存储会话 ID 和页面浏览次数:

kotlin
@Serializable
data class UserSession(val id: String, val count: Int)

如果您打算使用多个会话,则需要创建多个数据类。

要使用 Cookie 传递会话数据,请在 install(Sessions) 块中调用 cookie 函数,并指定名称 and 数据类:

kotlin
install(Sessions) {
    cookie<UserSession>("user_session")
}

在上面的示例中,会话数据将使用添加到 Set-Cookie 标头的 user_session 特性传递给客户端。您可以通过在 cookie 块中传递其他 Cookie 特性来配置它们。例如,下面的代码片段显示了如何指定 Cookie 的路径和过期时间:

kotlin
install(Sessions) {
    cookie<UserSession>("user_session") {
        cookie.path = "/"
        cookie.maxAgeInSeconds = 10
    }
}

如果所需的特性没有被显式公开,请使用 extensions 属性。例如,您可以按以下方式传递 SameSite 特性:

kotlin
install(Sessions) {
    cookie<UserSession>("user_session") {
        cookie.extensions["SameSite"] = "lax"
    }
}

要了解有关可用配置设置的更多信息,请参阅 CookieConfiguration

在将应用 部署 到生产环境之前,请确保将 secure 属性设置为 true。这使得 Cookie 仅能通过 安全连接 传输,并保护会话数据免受 HTTPS 降级攻击。

要使用自定义标头传递会话数据,请在 install(Sessions) 块中调用 header 函数,并指定名称和数据类:

kotlin
install(Sessions) {
    header<CartSession>("cart_session")
}

在上面的示例中,会话数据将使用 cart_session 自定义标头传递给客户端。 在客户端,您需要为每个请求附加此标头以获取会话数据。

如果您使用 CORS 插件来处理跨域请求,请按如下方式将会话自定义标头添加到 CORS 配置中:

kotlin
install(CORS) {
    allowHeader("cart_session")
    exposeHeader("cart_session")
}

仅在修改时发送会话数据

默认情况下,Ktor 会在每个响应中发送会话数据,即使它没有改变。

若要仅在会话数据被修改时发送它,请在会话配置中启用 sendOnlyIfModified 标志:

kotlin
install(Sessions) {
    cookie<MySession>("SESSION") {
        sendOnlyIfModified = true
    }
}

此选项对于基于 Cookie标头 的会话均可用。

存储会话有效负载:客户端与服务器

在 Ktor 中,您可以通过两种方式管理会话数据:

  • 在客户端和服务器之间传递会话数据

    如果您仅将会话名称传递给 cookie 或 header 函数,会话数据将在客户端和服务器之间传递。在这种情况下,您需要对会话的有效负载进行 签名并加密,以保护传递给客户端的敏感会话数据。

  • 在服务器上存储会话数据,并且仅在客户端和服务器之间传递会话 ID

    在这种情况下,您可以选择在服务器上 存储有效负载的位置。例如,您可以将会话数据存储在内存中、指定的文件夹中,或者您可以实现自己的自定义存储。

在服务器上存储会话有效负载

Ktor 允许您 在服务器上 存储会话数据,并且仅在服务器和客户端之间传递会话 ID。在这种情况下,您可以选择在服务器上保存有效负载的位置。

内存存储

SessionStorageMemory 允许在内存中存储会话内容。这种存储在服务器运行时保留数据,并在服务器停止后丢弃信息。例如,您可以按如下方式在服务器内存中存储 Cookie:

kotlin
cookie<CartSession>("cart_session", SessionStorageMemory()) {
}

您可以在此处找到完整示例:session-cookie-server

请注意,SessionStorageMemory 仅用于开发。

目录存储

directorySessionStorage 可用于将会话数据存储在指定目录下的文件中。例如,要将会话数据存储在 build/.sessions 目录下的文件中,请按以下方式创建 directorySessionStorage

kotlin
header<CartSession>("cart_session", directorySessionStorage(File("build/.sessions"))) {
}

You can find the full example here: session-header-server.

自定义存储

Ktor 提供了 SessionStorage 接口,允许您实现自定义存储。

kotlin
interface SessionStorage {
    suspend fun invalidate(id: String)
    suspend fun write(id: String, value: String)
    suspend fun read(id: String): String
}

这三个函数都是 挂起 函数。您可以参考 SessionStorageMemory

保护会话数据

签名会话数据

对会话数据进行签名可以防止修改会话内容,但允许用户查看这些内容。 要对会话进行签名,请将签名密钥传递给 SessionTransportTransformerMessageAuthentication 构造函数,并将此实例传递给 transform 函数:

kotlin
install(Sessions) {
    val secretSignKey = hex("6819b57a326945c1968f45236589")
    cookie<CartSession>("cart_session", SessionStorageMemory()) {
        cookie.path = "/"
        transform(SessionTransportTransformerMessageAuthentication(secretSignKey))
    }
}

SessionTransportTransformerMessageAuthentication 使用 HmacSHA256 作为默认身份验证算法,该算法是可以更改的。

签名并加密会话数据

对会话数据进行签名并加密可以防止读取和修改会话内容。 要对会话进行签名并加密,请将签名/加密密钥传递给 SessionTransportTransformerEncrypt 构造函数,并将此实例传递给 transform 函数:

kotlin
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 使用 AESHmacSHA256 算法,这些算法是可以更改的。

请注意,不应在代码中指定签名/加密密钥。您可以在 配置文件 中使用自定义组来存储签名/加密密钥,并使用 环境变量 对其进行初始化。

获取并设置会话内容

要为特定 路由 设置会话内容,请使用 call.sessions 属性。set 方法允许您创建一个新的会话实例:

kotlin
get("/login") {
    call.sessions.set(UserSession(id = "123abc", count = 0))
    call.respondRedirect("/user")
}

要获取会话内容,您可以调用 get,并接收一个已注册的会话类型作为类型参数:

kotlin
get("/user") {
    val userSession = call.sessions.get<UserSession>()
    if (userSession != null) {
}

要修改会话(例如,递增计数器),您需要调用数据类的 copy 方法:

kotlin
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 函数:

kotlin
get("/logout") {
    call.sessions.clear<UserSession>()
    call.respondRedirect("/user")
}

您可以在此处找到完整示例:session-cookie-client

延迟会话检索

默认情况下,Ktor 会尝试为每个包含会话的请求从存储中读取会话,而不管路由是否实际需要它。这种行为可能会导致不必要的开销 —— 特别是在使用自定义会话存储的应用中。

您可以通过启用 io.ktor.server.sessions.deferred 系统属性来延迟会话加载:

kotlin
System.setProperty("io.ktor.server.sessions.deferred", "true")

示例

下面的可运行示例演示了如何使用 Sessions 插件: