Client/Server Sessions

预计阅读时间:4分钟

Client-side/Server-side sessions (Session Content vs Session Id)

Ktor允许您传输会话内容或会话ID.

根据应用程序,有效负载的大小和安全性,您可能希望将会话的有效负载放入客户端或服务器中.

Client-side sessions and transforms (Sending Session Content)

对于cookieheader方法,如果没有其他参数,则将会话配置为将有效负载保留在客户端. 完整的有效负载将被来回发送. 在这种模式下,您可以并且应该应用转换来加密或认证会话:

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

如果您的有效负载不会遭受重放攻击,则仅应使用客户端会话. 另外,如果需要防止修改,请确保至少使用身份验证来转换会话,但最好还是使用加密. 如果您安全保存密钥,这应该可以防止有效载荷修改. 但是请记住,如果您的密钥被盗用,因此您必须更改密钥,则之前的所有会话都将被标记为无效.

客户端会话使用转换器来操纵有效负载,例如进行身份验证和/或加密.

您可以在" 变压器"页面上查看标准可用变压器的列表,以及更多信息.

Server-side sessions and storages (Sending Session Id)

如果指定存储,那么将使用该存储将会话配置为存储在服务器上,并且将在服务器和客户端之间传输sessionId而不是完整的有效负载:

application.install(Sessions) {
    cookie<MySession>("SESSION", storage = SessionStorageMemory())
} 

Security examples for client-side sessions

如果计划使用客户端会话,则需要了解它的安全隐患. 您必须妥善保管您的秘密哈希/加密密钥,就像它们被泄露一样,拥有密钥的人可能会冒充任何用户. 这也是一个问题,因为更改密钥将使先前生成的所有会话无效.

Good usages for client-side cookies:

  • 存储用户首选项,例如语言,Cookie接受度以及诸如此类.

    对此没有安全性问题. 只是喜好. 如果有人可以修改会话. 完全没有伤害.

  • 购物车信息

    如果此信息充当愿望清单 ,则就像偏好一样. 在这里不可能造成任何伤害.

  • 使用不可变的用户ID或电子邮件地址存储用户登录信息.

    如果至少通过了身份验证(并且具有一般风险的知识),应该可以,因为在正常情况下,人们将无法更改它以冒充他人. 而且,如果您使用旧的会话有效负载来存储唯一的不可变会话ID,它将仅向已经拥有访问权限的您自己的用户提供访问权限.

Bad usages for client-side cookies:

  • 使用会话作为缓存. 例如,存储用户的可兑换积分.

    例如,如果您使用会话作为缓存来阻止从数据库中读取数据,那么用户可以用来购买商品的用户积分 . 它是可利用的,因为用户可以购买商品,但不能更新会话或使用具有更多积分的旧会话有效负载.

  • 使用会话存储可变的用户名.

    考虑是否在会话中存储用户名以保留登录信息. 但也允许更改实际用户的用户名. 恶意用户可以创建一个帐户,然后多次重命名其用户,以存储每个用户名的有效会话有效负载. 因此,如果使用以前重命名的用户名创建新用户,则恶意用户将有权访问该帐户. 服务器端会话也容易受到此攻击,但是攻击者必须使这些会话保持活动状态.

Invalidating Client-side sessions

由于不能像服务器会话一样直接使客户端会话无效. 您可以通过将过期时间戳记作为会话有效内容的一部分来手动标记会话的过期时间.

例如:

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

fun Application.main() {
    routing {
        get("/user/panel") {
            val session = call.getMyExpirableSession()
            call.respondText("Welcome ${session.name}")
        }
    }
}

fun ApplicationCall.getMyExpirableSession(): MyExpirableSession {
    val session = sessions.get<MyExpirableSession>() ?: error("No session found")
    if (System.currentTimeMillis() > session.expiration) {
        error("Session expired")
    }
    return session
}

by  ICOPY.SITE