セッションとクッキーについて理解する

セッション

HTTP はステートレスなプロトコル

HTTP は、ステートレスなプロトコルです。ステートレスとは「状態を持たない」という意味で、ある通信とそれ以前の通信の間では、状態を共有しないことを表します。基本的に、クライアントとサーバーの間では、以前どのようなやり取りをしたかという情報は保持されません。同じリクエストを送信した場合は、必ず同じ結果が返されるようになっています。

Image from Gyazo

当初の HTTP の設計では、たくさんの処理を迅速にこなすために、このようなシンプルな設計になっていました。ところが Web が発展するにつれて、ステートレスではなく、ステートフルに通信したいというニーズが次第に生まれてくるようになりました。

例えば、ショッピングサイトを作る場合は、一度ログインしたらログアウトするまでの間は、認証状態が継続されるような仕組みが必要です。しかし、先ほど述べたように HTTP というプロトコルレベルでは、各クライアントの状態をサーバーが保持することは不可能なので、別のページに進むたびにユーザー名やパスワードを送信してもらう必要が出てきてしまいます。

そこで、このような問題を解決するために「セッション管理」という方法が誕生しました。

セッション管理

セッション」とは、ユーザーが Web サイトを訪問してから離脱するまでの一連の操作や通信のことです。

セッションとは、活動期間、学期、会期、開催期間、集会などの意味を持つ英単語。IT の分野では、コンピュータシステムやネットワーク通信において、接続/ログインしてから、切断/ログオフするまでの、一連の操作や通信のことをこのように呼ぶ。

引用: IT 用語辞典 e-Words - セッション 【session】

ショッピングサイトの例で言うと、ユーザーはログインしてからログアウトするまで、Web サイト内をあちこち移動しながら商品の選択・注文・決済という流れをたどります。このような、ユーザーによって行われる一連の操作のことをセッションと呼びます。

そして「セッション管理」とは、そのセッション内における通信相手であるユーザーをきちんと識別し、そのユーザーの状態を管理することを意味します。一般的に、セッション管理はサーバーとクライアントの間で「セッション ID」と呼ばれる識別番号をやり取りすることで実現します。

Image from Gyazo

大まかなセッション管理の流れは、次のようになります。

  1. クライアントはサーバーに初回リクエストを送る
  2. サーバーはセッション ID を発行する。そしてレスポンスと共に、発行したセッション ID をクライアントに送る
  3. クライアントはサーバーに再度リクエストする。このとき、リクエストにはセッション ID を付加する
  4. サーバーは、送られてきたセッション ID を見て ① でリクエストを出したクライアントと同一クライアントであると認識し、レスポンスを返す

上記の流れにあるように、クライアントは「セッション ID」と呼ばれるセッションを識別するための ID をサーバー側から受け取ります。そして以降のリクエストからは、受け取ったセッション ID をリクエストに添えて送信することで、一連の通信が同一のクライアントからであることをサーバー側に認識させることができます。

また、セッション ID をやり取りする方法は、代表的なものとして 3 つあります。

  1. クエリ文字列にセッション ID を埋め込む
  2. hidden フィールドにセッション ID を埋め込む
  3. Cookie にセッション ID を埋め込む

この内、最もよく利用されるのは Cookie による方法ですが、まずはクエリ文字列と hidden フィールドを利用した方法について説明したいと思います。

クエリ文字列にセッション ID を埋め込む

まずは、クエリ文字列にセッション ID を埋め込む方法についてです。

クエリ文字列」とは、クライアントがサーバーにデータを渡すために、URL の末尾に ?foo=value1&bar=value2 というような形式で付加する文字列のことです。

通常、サーバーはリクエストに応じて Web サイトの HTML ソースを返します。このとき、HTML ソースの中にある全てのリンクに対して、次のようにセッション ID を付けます。

http://example/home?session-id=123

そうすることで、ユーザーが Web サイト内のボタンやリンクをクリックしたときに、セッション ID が含まれたリクエストが送信されるようになります。 こうしてサーバーは、セッション ID を受け取ることができます。

ただし、この方法には次のような問題点があります。

  • ブラウザの URL バーにセッション ID が表示されるため、ユーザーによる参照・変更が容易である
  • URL の一部にセッション ID が含まれるため、キャッシュ・ログ・ Referer ヘッダなどを通じて第三者に漏洩する危険がある

そのため、クエリ文字列にセッション ID を埋め込む方法は、基本的には非推奨となっています。

hidden フィールドにセッション ID を埋め込む

次は、hidden フィールドにセッション ID を埋め込む方法についてです。

hidden フィールド」とは、ブラウザの画面には表示されない HTML のフォームの項目のことです。別名、隠しフィールドとも呼ばれます。

この方法では、すべてのページ遷移をフォームデータを送信する形で記述し、フォーム中の hidden フィールドにセッション ID を埋め込みます。例えば、次のようなイメージです。

<form>
  <input type="hidden" name="session-id" value="123" />
</form>

こうすることで、ユーザーがボタンなどをクリックしたときに、フォームデータの一部としてセッション ID がサーバーに送信されるようになります。

この方法は、先述したクエリ文字列に埋め込む方法に比べて、第三者に流出する事故が起こる要因は少ないです。しかし、ページ遷移をすべてフォームで記述する必要があり、HTML の記述が煩雑になってしまうという問題点があります。

そのため、セッション管理を行うときは、次に解説する Cookie を利用した方法が一般的によく用いられます。

Cookie にセッション ID を埋め込む

最後は、Cookie にセッション ID を埋め込む方法についてです。

Cookie」とは、主にサーバーからクライアントに対して送信され、クライアントのハードドライブ上に保存される小さな文字列データのことです。Cookie を利用することで、サーバーはクライアントのコンピュータにデータを保存し、クライアントはその後のリクエストで自動的にサーバーに対してデータを送り出すということができます。

Cookie は、これまで紹介した方法の中で最もプログラミングの手間が少ないこともあり、セッション ID をやり取りする際には多く用いられます。具体的には、次のような流れでやり取りをします。

Image from Gyazo

まず、クライアントはサーバーに対して初回リクエストを送ります。これを受けてサーバーは、セッション ID を発行して、レスポンスと共に送り返します。このとき、サーバーはレスポンスヘッダーに Set-Cookie というフィールドを含め、そこに Cookie として保存させたい情報を設定します。例えば、上記の図のように「Set-Cookie: sessionid=123;」と記述すれば、「sessionid: 123;」という情報を Cookie として保存してほしいとブラウザに示すことができます。

Image from Gyazo

レスポンスを受け取ったブラウザは、Set-Cookie ヘッダーを見て、その値を自分の PC のハードドライブ上にテキストファイルとして保存します。そしてブラウザは、再びサーバーに対してリクエストを送るときに、保存した Cookie 情報をリクエストヘッダーの Cookie フィールドに含めます。そうすると、サーバーは送られてきた Cookie からセッション ID を受け取ることができます。

このように、Cookie を保持するクライアントは、サーバーにリクエストを送信するタイミングで、常に Cookie を送信します。そしてサーバーは、これを適宜参照することで Cookie の値を取得することができます。このような流れでセッション ID を交換することで、サーバーは以前通信したクライアントと同一であることを認識することができるというわけです。

ただし、Cookie を利用する際にも次のような注意点があります。

  • すべてのクライアントが Cookie を許可しているわけではないため、使えない場合もある
  • 暗号化しない HTTP リクエストで送信した場合、Cookie が第三者に盗聴される危険性がある
  • XSS の脆弱性がある場合、JavaScript 経由で Cookie にアクセスできてしまう恐れがある

そのため、Cookie を利用するときは、Secure 属性(暗号化された HTTPS で通信するときのみ Cookie を送信するように求める属性)や、 HttpOnly 属性(XSS によって Cookie が盗まれるのを防ぐ属性)などを対策として付ける必要があります。

参考文献

buymeacoffee