Webアプリを開発するに当たって、「自分でセッションを書きたい」なんて思う人はおそらく1%もいないでしょう。とはいえ、標準で備わっているセッション機能に問題や機能不足があって、手を入れざるを得なくなる、という場面も多々存在してしまいます。ここでは、セッションまわりでよく問題になる部分について触れてみましょう。
そもそも、セッションとは
Webアプリにおけるセッションは、
- 接続ごとに固有の識別子(セッションID)を割り当て、その接続を使った通信のたびにセッションIDを送受信する
- セッションIDと紐付ける形でデータを持って、アクセスごとに値を読み出す。
- データが書き変われば、書き戻す。
というような流れで、「接続ごとにデータを保存する」ということを実現しています。
セッションとCookie
上の1にあるような「通信のたびにセッションIDを送受信する」ために使われるのがCookieです。ただ、CookieはHTTPヘッダに含まれるもので、いくつか取得するための経路が存在してしまいます。それを防ぐために、
- Cookieの
secure
属性とSSL(盗聴を防ぐため、HTTPS上での通信でしかCookieをやり取りしない) - Cookieの
httponly
属性(JavaScriptから読み書きさせない)
などのような防御策が存在しています。
セッションデータのCookie保管
Railsのデフォルトなどのように、セッションIDだけでなくてセッションの値までCookieに詰め込んでしまう、という実装も存在します。サーバ側でデータ保管しなくていいという意味では楽なのですが、
- ユーザー側にデータそのものが送信されるので、(暗号化や認証があるとはいえ)中身の盗聴や改竄の危険がある。
- Cookieは4キロバイト制限があるので、そんなにデータを入れられない
- Cookieは通信のたびにクライアントとサーバの間を行き来するので、データを増やすと負荷になる
- Cookieのデータを保存しておくと、同じセッションを復元できてしまう(サーバ側から無効化する術がない)
というような問題があるので、ログインが必要になる、あるいは買い物するようなサイトではおすすめできないものです。どうしても使う場合は、最低限
- Cookie内のセッションデータを暗号化する
- Cookie内のデータとして最終更新日時やユーザーエージェントのようなものを持たせて、明らかに異常値なら即座に破棄する
といった処置が必要でしょう。
セッションデータを別置きにする場合
CookieにはセッションIDだけを保存して、実データはRDBやKVSなどに格納しておくという場合、容量制限などの問題は発生しませんが、それでもいろいろと考えることは多いです。
セッション固定攻撃とIDの更新
「セッション固定攻撃」といって、攻撃者が適当なセッションIDを取得して、仕込みを行った上で、ターゲットに同じセッションIDを使わせることでセッションを乗っ取るという攻撃手法が存在しますが、PHPでは5.5以前ででたらめなセッションIDでも受け入れてしまうというような問題があります。
それはともかく、このようなセッション固定攻撃、そしてセッションIDが盗まれることへの改善策として、「セッションが続いていてもIDをときどき更新する」ということがあります。このときに、新しいIDを発行した直後に古いIDを無効としてしまうと、Ajaxや画像などでほぼ同時にリクエストが発生した場合に、
- リソースAへのアクセスが発生(旧セッションID)
- リソースBへのアクセスが発生(旧セッションID)
- リソースAへのアクセスがサーバに到着(新セッションIDに切り替え)
- リソースBへのアクセスがサーバに到着(旧セッションIDはすでに無効化されていてエラー)
というような流れで、セッションが切れてしまうことがあります。さらに一定時間は旧セッションでも受け入れて新セッションに変換するようにしましょう。
セッションデータのGC
きちっとログアウトすればサーバ側のセッションデータも破棄されますが、ブラウザを閉じる前にすべてのサイトからログアウトする人はそう多くないでしょうし、ログアウト状態でもセッションを発行するようなサイトでは、通常消す方法は用意されていません。
そんな訳で、途中で切れたセッションのデータが残り続けることになってしまいますので、何かしらの方法でGCをかける必要があります。もっとも、Redisではデータ自体に有効期間を指定できるので、正しく使えば期限切れで自動的に消えていきます。RailsのActiveRecordSessionでは残り続けるようです。