セッション
Session ・ せっしょん
ログイン中の利用者の状態をサーバ側に保管し、Cookie で渡した ID で毎回結び付ける仕組み。
✦ 30秒で試す — 航海毎回「初めまして」のはずの通信が、なぜ覚えている?番号札を持った要求がサーバ2に振られると?予想してみる概要
セッションは、利用者がログインしてからログアウトするまでの「つながり」をサーバ側で管理する仕組みです。ログインに成功すると、サーバは「セッション ID」と呼ばれるランダムで推測できない文字列を発行し、Cookie としてブラウザに渡します。利用者が誰で、いつログインして、カートに何を入れたか。そうした実際の状態はサーバ側に保管し、ブラウザには「引換券」である ID だけを持たせるのがポイントです。
EC サイトの買い物かご、管理画面のログイン状態、入力途中のフォームの値など、「ページをまたいで覚えていてほしい情報」のほとんどはセッションの上に載っています。Web アプリの認証を実装するとき、最初に向き合うことになる語彙です。
なぜ生まれたか
HTTP はステートレスなプロトコルとして設計されました。リクエストとレスポンスの1往復が終わるたびに関係は切れ、サーバは「さっきの人がまた来た」ということを覚えていません。文書を配るだけの初期の Web ではそれで十分でしたが、ログインや買い物かごのような「状態を持つアプリケーション」を作ろうとした途端、この健忘症が問題になりました。
毎回 ID とパスワードを送らせるのは危険で不便です。かといって、利用者の情報をすべてブラウザ側に持たせると、書き換え放題になってしまいます。そこで「本体はサーバが預かり、ブラウザには番号札だけ渡す」という分担が考え出されました。番号札の受け渡し手段として Cookie がちょうど同じ時期(1990年代半ばの Netscape)に生まれたことで、セッション方式は Web の標準的なパターンとして定着しました。
詳細
発行と照合の往復
セッションの一生は「ログイン時の発行」と「以降のリクエストごとの照合」の2つの局面でできています。
ブラウザは同じサイトへのリクエストに Cookie を自動で添付するため、利用者は何も意識しません。サーバは受け取った ID をセッションストアで引き、対応するデータが見つかれば「ログイン済みの本人」として扱います。ログアウトはストア側のデータを消すだけで済み、番号札はその場で無効になります。この「サーバ側でいつでも失効できる」性質が、セッション方式の大きな強みです。
寿命 — アイドルタイムアウトと絶対タイムアウト
セッションには2種類の期限を設けるのが定石です。アイドルタイムアウトは「最後の操作から一定時間(たとえば30分)何もなければ切る」期限で、操作のたびに延びます。席を離れた端末を他人に使われる時間を短くするためのものです。絶対タイムアウトは「ログインから一定時間(たとえば12時間や1週間)たったら、操作を続けていても切る」期限で、盗まれたセッション ID が延々と使われ続けるのを防ぎます。アイドルだけだと、攻撃者が定期的にリクエストを送り続ければ永遠に生き延びてしまうので、両方を組み合わせます。
Cookie の Max-Age は「ブラウザがいつまで ID を覚えているか」にすぎません。本当の期限はサーバ側のストアで管理し、期限切れの ID が送られてきたら拒否するのが正しい形です。
セッションストアをどこに置くか
状態の実体を保存する場所(セッションストア)の選び方は、地味に見えて重要な設計判断です。いちばん手軽なのはアプリのプロセスのメモリの中ですが、サーバを再起動すると全員がログアウトし、何よりサーバを複数台に増やした瞬間に破綻します。ロードバランサが次のリクエストを別のサーバに振り分けると、そこにはセッションデータがないからです。
対策は大きく2つあります。ひとつは同じ利用者をいつも同じサーバへ送る「スティッキーセッション」です(ロードバランサが Cookie や送信元 IP を見て振り分け先を固定します)。手軽ですが、そのサーバを外したり落としたりすると、載っていたセッションはまとめて消えます。台数を増やしても既存の利用者は元のサーバに張り付いたままなので、負荷の偏りも直りにくく、スケーリングの柔軟さを損ないます。
もうひとつは、Redis のようなインメモリのキャッシュストアや RDBMS にセッションを外出しし、どのサーバからも同じストアを参照する構成です。アプリサーバは状態を持たなくなるので、何台に増やしても、1台を入れ替えても、利用者はログインしたままです。実務ではこちらが主流で、「状態をアプリサーバから引き剥がす」というスケールアウトの定石の典型例になっています。その代わり、ストアが単一障害点にならないよう冗長化し、リクエストのたびにストアへ1往復する遅れを見込む必要があります。
下の部品で、サーバを足したり外したりしながらページを開いてみてください。セッションの置き場所を切り替えると、同じ操作でログインが続くか切れるかが変わります。
まずログインのチップをつかんで、ロードバランサへ投げ込んでください(タップでおまかせ)。いまはサーバ1台で、セッションはサーバのメモリに置きます。
ステートレスなトークンとの対比
セッションの対極にあるのが、JWT に代表される「サーバに状態を持たない」方式です。利用者の情報を署名付きのトークンに焼き込んでブラウザに渡してしまえば、サーバはストアを引かずに署名の検証だけで本人確認ができます。ストアが要らなくなる代わりに、「発行済みのトークンをすぐに無効化しにくい」という弱点を抱えます。セッションは照合のたびにストアへのアクセスが走るが失効は自在、JWT は検証が軽いが失効が難しい。この対称関係を押さえておくと、認証まわりのアーキテクチャの議論が一気に読みやすくなります。
セキュリティ上の落とし穴
セッション ID は「持っているだけで本人になれる」鍵なので、盗まれれば乗っ取り(セッションハイジャック)が成立します。通信経路での盗聴は HTTPS と Cookie の Secure 属性で防ぎ、XSS によるスクリプトからの窃取は HttpOnly 属性で防ぎます。ブラウザが Cookie を自動で送る性質そのものを悪用するのが CSRF で、SameSite属性を明示し、重要な操作にはトークンによる確認を重ねます。
もうひとつ忘れがちなのが、「ログインに成功したらセッション ID を必ず振り直す」ことです。これを怠ると、攻撃者があらかじめ仕込んだ ID のままログインさせ、その ID で後から入り込むセッション固定攻撃が成立します。権限が上がる場面(管理者への切り替えなど)でも同じように振り直します。ログアウトではサーバ側のデータを確実に消し、Cookie を消すだけで終わらせないことも大切です。
試問
ブラウザ(alice)からロードバランサ(要求をサーバへ振り分ける係)へ、チップを投げ込んで「ログイン」や「カートに入れる」を送ります。空き枠を押すとサーバが増え、サーバを押すと外れます(外したサーバのメモリは消えます)。セッション(ログイン中という状態)の置き場所は、各サーバのメモリ・同じ人を同じサーバへ送り続けるスティッキー・共有ストアから選べます。
まずログインのチップをつかんで、ロードバランサへ投げ込んでください(タップでおまかせ)。いまはサーバ1台で、セッションはサーバのメモリに置きます。
