クライアント/サーバーセッション

Estimated reading time: 1 minute

クライアントサイド/サーバーサイドセッション(セッションコンテンツ vs セッションID)

KtorはセッションコンテンツかセッションIDのいずれかを変換する機能を提供します。

アプリケーションやペイロードのサイズやセキュリティに応じて、クライアントかサーバにセッションのペイロードをいれたいかもしれません。

クライアントサイドセッションと変換(セッションコンテンツの送信)

cookieheaderメソッドに対し追加の引数を必要とすることなしに、セッションはクライアントにおいてペイロードを保持するよう設定できます。 ペイロード全体が送信、受信されるようになります。 このモードでは、セッションを暗号化・認証するために変換できます:

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

リプレイアタックの影響をうけないのであれば、クライアントサイドのセッションのみを使うべきです。 また、内容の修正を禁止する必要があるのであれば、最低でも認証を、理想的には暗号化も行った上でセッションを変換します。 シークレットキーが安全に保管されていれば、これによりペイロードの内容の変更を禁止できます。 しかし、キーが危険にさらされ、キーを変更する必要が出たとき、すべての過去のセッションは無効になることを忘れないでください。

クライアントサイドセッションはペイロードの操作のためにトランスフォーマーを利用します。 例えば認証や暗号化のために使います。

トランスフォーマーページを確認することで標準的な利用可能なトランスフォーマー一覧やその他情報が見れます。

サーバサイドセッションとストレージ(セッションIDの送信)

ストレージを指定すると、セッションはサーバ側にストレージを利用して保管されるよう設定され、 ペイロード全体の代わりにセッションIDがサーバ・クライアント間でやりとりされるようになります。

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

クライアントサイドセッションのためのセキュリティ例

クライアントサイドセッションを使う予定があるなら、それが持つセキュリティ的な意味合いを理解する必要があります。 ハッシュ・暗号化のためのシークレットキーを安全にする必要があり、 もし危険にさらされた場合は、キーを持つ人が任意のユーザになりすます潜在的な可能性があります。 また、キーを変えることで過去に生成したすべてのセッションが無効化されることも問題としてあります。

クライアントクッキーに関する良い使い方:

  • 例えば言語やCookieの承認設定などのユーザの設定を格納する

    これについてセキュリティに対する懸念はありません。ただの設定だからです。もし誰かがセッションを変更できたとしても、何も害はありません。

  • ショッピングカート情報

    もしこの情報がウィッシュリストとして動作するなら、設定のようなものです。害は及びません。

  • 不変なユーザーIDまたはEmailアドレスを使いユーザログイン情報を保存

    少なくともCookieが認証されている(そして一般的なリスクの知識がある)場合は問題ありません。 通常の状況では他の人になりすますように変更することはできないからです。 また、一意の不変のセッションIDを保存すると、古いセッションペイロードを使用すると、既にアクセス権を持っている自分のユーザーにアクセス権を与えるだけです。

クライアントクッキーに関する悪い使い方:

  • セッションをキャッシュとして使うこと。例えばユーザが持つ換金可能なポイントの保存に使うこと。

    データベースから読み込まないようにするため、セッションをキャッシュとして利用するのであれば、(例えばユーザが物の購入に使えるポイントなど) 悪用可能です。 ユーザはアイテムを購入しつつセッションを更新しないか、より多くのポイントを持つ古いセッションのペイロードを使い続けることができます。

  • 変更されうるユーザ名をセッションに保存すること

    ユーザ名をセッションに保存し、ログイン情報を保持することを考えてみてください。 ユーザ名の変更も許可するとします。 悪意のあるユーザがアカウントを作成し、ユーザーを何度か名前変更し各ユーザ名に対する有効なセッションペイロードを保存します。 新しいユーザが過去に名前変更されたユーザ名で作成された場合、悪意のあるユーザはそのアカウントにアクセスできます。 サーバサイドセッションもこれに対し脆弱ですが、この場合攻撃者はそのセッションを有効な状態で保持し続けておく必要があります。

クライアントサイドセッションの無効化

クライアントサイドセッションはサーバサイドセッションのように直接無効化することができないので、 セッションペイロードの一部として有効期限を含めることで有効期限をセッションに対し手動で設定することができます。

例:

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
}