fuka’s diary

A blog that shares my knowledge.

Session機構を理解する

この記事では、Session機構の知識を共有します。
Session機構は独自に実装しない場合でも、仕組みを知ることは大切です。

これは『良し悪しの議論ではない』ことを前提にした話ですが、PHPが提供するSession機構では以下の機能が提供されません。
・Session IDの失効
・バリデートチェック

SessionファイルはApache2などのwebサーバーが管理しており、サーバー側によって定期的に削除されるため、Session IDの失効はPHPのコード上からは操作できません。
バリデートチェックは、確かにSessionファイルがサーバーのweb公開ディレクトリー以外に保存されるため改竄の危険は低く、必要ないと言えるかも知れません。
しかし、これらの機能は独自の実装では必要不可欠ですので、注意しましょう。
また、独自に設置したコンピューターでPHPが提供するSession機構を用いている場合には、Sessionファイルのメンテナンスを意識するようにしてください。

Session機構とは

一言で言うと「ユーザー認証を簡略化した仕組み」です。

分かり易く書くなら、
クライアントから「金庫室へ入りたい」と要求があった場合、サーバー側で金庫の暗証番号を用意し、最新の暗証番号をユーザーに教えます。
暗証番号はしばらくは有効ですが、一定時間を超えると失効します。
ユーザーは、Sessionが続く限り、その金庫に暗証番号でログインすることができます。

考え方

下図がSession機構の基本的な動作です。
ここでは、分かり易くするためSessionデータの記述は省いています。

Session ID

サーバーが発行するSession ID、つまり暗証番号です。
クライアントからの要求の都度、Session IDを新たに発行します。

構成 日時と、一意かつ予測が困難な文字列
長さ 日時14桁の数字+ランダム値32バイトのhex値
妥当性 ^\d{14}[0-9a-z]{64}$
有効期限 30分

一度利用したSession IDは直ちに無効化するのではなく、しばらく残しておきましょう。
そうしないと、不安定なネットワーク環境下でページ遷移に失敗した時や、同時アクセスによって、Sessionの継続性が保証できなくなります。

なお、Session IDはディスク資源を消費するため定期的に削除が必要です。
加えて、Session情報をGET/POSTメソッドで受け渡す設計にはしないようにしましょう。

上記ではSession IDに日時を使用しています。
構造を全体的に見て堅牢であれば問題なく、かつ一意性を向上させる意図があります。

例えば、1日10人が使用するシステムで、0~99までの数値で構成されたSession IDを使用するとします。
この場合、最大10日でSession IDは消費し尽くされてしまいます。

一方、Session IDに日付を連結した場合はどうでしょうか。
この場合、理想的にはSession IDは消費し尽くされることはありません。

これと同じ考え方で、日時とランダム値を組み合わせることで一意性が向上するわけです。

ディレクトリー

もし、レンタルサーバーがweb公開用のディレクトリーのみを提供している場合、.htaccessファイルなどでディレクトリー構造へのアクセス制御が必要です。
さもないと、攻撃者にSessionファイルにアクセスされてしまうかも知れません。
web公開用のディレクトリーの上位階層であるユーザーディレクトリーにアクセスできるなら、ここにSession管理用のディレクトリーを用意しましょう。

Sessionデータ

Sessionが持つ、Sessionが続く限り持つことができるデータです。
例えば、カートの中身などが想定されます。
Session機構の思想にもよりますが、これはSessionが開始され、終了するまでの一時的なデータです。

Session Cookie

Session Cookieはクライアント側のブラウザーが持つSession IDです。
DomainとPathを正しく指定し、以下の設定を基本とします。

SameSite Lax
Secure 有効
HttpOnly 有効
Expires 0(ブラウザー終了まで有効)

もし、通信内容を保護できない場合、Secureを無効とし、SameSiteはLaxかStrictとします。
この場合、通信内容の傍受によってSession ID漏洩のリスクが常に内在することに留意しましょう。

運用形態

ファイル名

ファイル名を用いてSession機構を実装します。
ファイル名の長さと文字種の制限を受けますが、比較的容易に実装できます。
ただし、ファイル数に上限がある場合は注意してください。

データベース

データベースを用いてSession機構を実装します。
気を付ないとサーバーに負荷をかける場合がありますが、長く複雑なSession IDを利用できます。
テーブル構造は次のようなものが考えられます。

カラム
PRIMARY KEY sessionid CHAR(78)
validity tinyint(1)
expiry DATETIME
sessiondata CHAR(78)

【以下は、DBMSに詳しい人向けの話です】
Session IDを格納するカラムは主キーにするのが適切です。
わざわざ主キー用にSession ID以外のキーを割り当てる必要はありません。
なぜなら、Session IDは十分にユニークであるためです。
さらに、Session IDにnullは期待しないため、主キー以外を選択する理由がないのです。

主キーにSession IDを用いると、「いずれインデックスが劣化して検索が遅くなるのではないか?」と心配になるかも知れません。
しかし、インデックスの劣化は多くが迷信で、大抵は「インデックスとは、B-tree走査と等価である」と言う誤解によって生じています。

インデックスは、以下の3ステップを辿ります。
①B-treeの走査
②リーフノードチェーン
③データ取り出し

確かにB-treeの階層が深くなれば、①に時間を要することになりますが、その値は小さいものです。
それ以上に、②と③で多くのアクセスが発生するとインデックス検索の速度は著しく低下します。
これがインデックスが劣化したように見える原因となります。
※例えば、重複値がある場合や、全値検索では多くの場合でリーフノードチェーンを辿ることになり、データ取り出しも複数回生じることによってレスポンスがさらに低下します。

インデックスに不適なテーブル構造はレスポンスを低下させますが、ユニークなSessionではそれほど懸念しなくて良いのです。
テーブル設計時、Session IDの他に、Sessionデータ格納や、ユーザー認証やらを融合させて「ゴッドテーブル」を作り込まないように注意しましょう。
ゴッドテーブルは管理を煩雑にするだけでなく、データベースのレスポンスを低下させる原因を作り込んでしまう可能性があります。

自動ログイン

無理に自動ログインを実装する必要はありませんが、基本的にSession機構と同じ仕組みを用います。
自動ログイン専用のSessionとCookieを用意することで、Session IDのメンテナンスと切り分けることが可能です。

違いは有効期間が長いこと、最低でも2つ以上のCookieを持つことです。
Cookie1つの強度は16^64ですが、2つの強度は16^128であり、より堅牢です。
Cookieの数に比例して強度は上がりますが、長すぎるSession IDはオーバークオリティーとなるので注意が必要です。
また、重要なサービスの場合、自動ログインを使わないか、有効期間を適切な値にしましょう。

なお、Session機構にも言えることですが、Cookieを盗まれるとアウトです。
しかし、適切なドメインを指定し、httponlyが有効なCookieが盗み出せるような脆弱な環境では、どんな情報も保護できません。

使用機会にストイックになり過ぎない

Sessionは画像やPDFファイルなどのデータでも作動できます。
しかし、もし画像にもSession機構を適用している場合、100ファイルの画像を表示するページでは、100回のSession認証が行われることになります。
何でもかんでもSession管理の対象としないよう、ターゲットを限定して運用しましょう。
画像やPDFは例えば別のサーバーへは位置し、リファラーでアクセス制御する方法が考えられます。