Ktor 从入门到放弃(五) session 管理

在 Web 开发中 Session 是非常常用也是必要的东西,本篇即来实现在 Ktor 下完成 Session 的操作。

首先我们需要将 Session 安装至框架,在 Ktor 中由于解藕的存在,各个功能均是被安装进去的,在不进行安装的情况下,无法使用 Session(上一篇讲到的 FreeMarker 也需要先安装)。

data class MySession(val name: String, val data: String)

@KtorExperimentalAPI
fun Application.main() {
    install(Sessions) {
        cookie<MySession>("MySession", directorySessionStorage(File(".sessions"))) {
            cookie.path = "/"
        }
    }
}

上面的 directorySessionStorage() 来自 Ktor 的 Session 库,并且需要注意的是,directorySessionStorage()也是一个 Experimental 的 API,需要加入注解来使其能够顺利编译:

compile "io.ktor:ktor-server-sessions:$ktor_version"

在上面这段代码里,即表示了这个 Session 可以在文件系统里保存,并且作用范围是全站,即以 / 为路径的所有请求。这意味着我们可以通过请求路径来进行 Session 的隔离。

对于只需要使用 Session,而不需要其他配置的情况下,上面的代码也可以简单的写成:

fun Application.main() {
    install(Sessions) {
        cookie<MySession>("MySession")
    }
}

然后来写一段代码验证一下 Session 的工作情况:

data class MySession(val name: String, val data: String)

@KtorExperimentalAPI
fun Application.main() {
    install(Sessions) {
        cookie<MySession>("MySession", directorySessionStorage(File(".sessions"))) {
            cookie.path = "/"
        }
    }
    get("/session") {
        val s = call.sessions.get("MySession") as? MySession
        if (s == null) {
            call.sessions.set("MySession", MySession("rarnu", "init"))
            call.respondText { "generated new session" }
        } else {
            call.respondText { "name: ${s.name}, data: ${s.data}" }
        }
    }
}

编译运行后,在浏览器内访问 http://localhost:8080/session 即可看到效果,第一次进入时,显示 generated new session 而页面刷新后显示 Session 的信息。

同时,我们可以在工程目录下发现被保存下来的 Session 内容:

你可以尝试删掉这个文件看看会发生什么。


在 Ktor 内,拥有几种不同的 Session 管理方式,上面用的是 Cookie,即把数据保存到本地。在很多场景下,我们还会有另外一种请求方式,即把相关的数据放在 Header 里,通常是用于 API 或 XHR 请求,这个时候我们可以使用 header() 来描述 Session:

fun Application.main() {
    install(Sessions) {
        header<MySession>("MySession") {
            transform(SessionTransportTransformerMessageAuthentication(SecretKeySpec(key, "HmacSHA256")))
        }
    }
}

这里的 key 是一个 ByteArray 对象,也就是加密用的 key,它可以是任意组合的 byte 串。后面的 HmacSHA256 是采用的算法,具体可以使用哪些算法可以查阅文档。当然此处在文档里有一处 bug,在 Ktor 官方文档内,用于 Header 的 transformSessionTransportTransformerDigest,而这个类并不安全,在 Ktor 内对它的描述如下:

@Deprecated(
    "This authentication kind is potentially vulnerable with several hash functions." +
        " Use SessionTransportTransformerMessageAuthentication instead or ensure you are using secure enough hash."
)

为了安全起见,应当使用此处的 SessionTransportTransformerMessageAuthentication 并配合相应的加密手段。


上面的讲的事情都是在服务器端做的,然而很多时候我们会将 cookie 保存在客户端,这个时候要怎么做呢?其实也很简单的,把上面讲的内容稍做结合就可以了:

fun Application.main() {
    install(Sessions) {
        cookie<MySession>("MySession") {
            val secretSignKey = hex("000102030405060708090a0b0c0d0e0f")
            transform(SessionTransportTransformerMessageAuthentication(secretSignKey))
        }
    }
}

此时我们就拥有了写到客户端的 Cookie 了。当然了,对于客户端 Cookie 有一点很重要,就是校验是否过期,这里也提供一个简单的函数:

data class MySession(val name: String, val expiration: Long)

fun ApplicationCall.expiration(): Boolean {
    var ret = true
    val s = sessions.get(sessionName) as? MySession
    if (s != null && System.currentTimeMillis() < s.expiration) {
        ret = false
    }
    return ret
}

现在我们可以在 Ktor 程序内自由的使用 Session 了,本篇到此结束。


下一篇预告:《Ktor 从入门到放弃(六) WebSockets》

推荐阅读更多精彩内容