Cookieとセッションの違いとは?保存する場所と使い分けをわかりやすく解説

最終更新日

Schoo Java入門 中級 第8回 Webアプリケーションにおける状態管理

セッションは、リクエストスコープと同じく、サーバー側にある「情報の置き場所」です。リクエストスコープが今回のリクエストで使う置き場所なのに対し、セッションは、次のリクエストでも同じ情報を使える置き場所です。

では、次に画面を開いたとき、サーバーはどのセッションを使えばよいのでしょうか。そこで使うのがCookieに保存したセッションIDです。ブラウザがIDを送ることで、サーバーは前回と同じセッションを見つけられます。情報を置くのがセッション、その置き場所を識別するIDを運ぶのがCookieという関係です。

たとえば「一覧をコンパクト表示にする」という好みは、設定値をCookieへ保存できます。一方、「本人確認に成功した利用者は誰か」は、サーバーのセッションへ保存する構成にできます。保存場所だけでなく、その値を何の判断に使うのかが使い分けのポイントです。

本記事は、Schoo「Java入門 中級」第8回「Webアプリケーションにおける状態管理」に対応しています。ここでいうセッションは、JavaのServletでHttpSessionを使う構成です。授業を未受講の方にも、表示設定とログイン情報の具体例で違いを説明します。

リクエストスコープもセッションも、サーバー側の情報置き場

たとえば、Servletで取得したタスク一覧をJSPへ渡す場合は、リクエストスコープへ保存できます。一方、ログインした利用者のIDを、一覧画面でも詳細画面でも使いたい場合は、セッションへ保存できます。違うのは「情報を置けるか」ではなく、「どこまで同じ情報を使い続けるか」です。

比較する点リクエストスコープセッションスコープ
保存先サーバー側のrequestオブジェクトサーバー側のHttpSessionオブジェクト
使える範囲1回のリクエスト。同じリクエストでforwardした先でも使える同じ有効なセッションに結び付いた複数のリクエスト
保存の例今回の一覧画面に表示するタスク一覧複数の画面で使う、ログイン済みの利用者ID
保存・取得request.setAttribute / request.getAttributesession.setAttribute / session.getAttribute

一覧画面のリンクから詳細画面を開くと、新しいリクエストになります。前のリクエストに保存した属性は、新しいリクエストには自動で引き継がれません。リダイレクトも新しいリクエストです。一方、同じ有効なセッションが識別されれば、セッションに保存した利用者IDは詳細画面でも取り出せます。

なお、request属性もsession属性もサーバー側で保存する情報です。ブラウザがフォームなどで送るリクエストパラメータとは別です。また、セッションはログイン専用ではなく、複数のリクエストで共有したい情報を置くために使えます。

Cookieの役割は、次のリクエストにセッションIDを付けること

サーバーには複数のセッションがあります。リクエストが届いただけでは、以前使っていたセッションと自動的に結び付くわけではありません。ここでは、CookieでセッションIDを受け渡す一般的な構成を見ます。

  1. サーバー側でセッションを用意し、識別用のセッションIDを割り当てる。ログインに成功したら、そのセッションへ利用者IDを保存する。
  2. サーバーはレスポンスで、セッションIDをJSESSIONIDというCookieへ保存するようブラウザに指示する。
  3. 次の画面を開くと、ブラウザは送信先の条件に合うJSESSIONIDのCookieをリクエストに自動で付ける。
  4. サーバーは届いたIDから同じセッションを見つけ、保存済みの利用者IDを取り出せるようにする。

このIDの発行やCookieの受け渡し、セッションとの対応付けは、通常Servletを動かすサーバーが処理します。アプリ側で毎回JSESSIONIDのCookieを探して、利用者情報を検索するコードを書く必要はありません。Servletではrequest.getSession(false)などで既存のセッションを取得し、getAttributeで情報を読みます。

イラスト:CookieのIDで、前回と同じセッションを見つける

JSESSIONID=A1は「どのセッションか」を示すID、loginUserId=1001は「誰としてログインしたか」という保存済みの情報です。この2つは別物です。Cookieで送るのはセッションIDであり、セッション内の利用者情報を丸ごとブラウザへ移すわけではありません。

この構成でJSESSIONIDのCookieが送られなければ、そのCookieを使って以前のセッションを識別することはできません。ただし、Cookieがなくなった瞬間にサーバー側の情報も消えるわけではありません。また、IDが届いても、対応するセッションが期限切れなら以前の情報は使えません。

Cookieとセッションの違いを、保存先から整理する

比較する点Cookieセッション
データの保存先ブラウザ側サーバー側
今回の保存例displayMode=compactという表示設定loginUserId=1001という認証済みの利用者情報
次のリクエストで使う方法ブラウザが条件に合うCookieを送信するセッションIDに対応するサーバー側の保存先から取り出す
Javaで使うものCookieクラス、addCookie、getCookiesHttpSession、setAttribute、getAttribute
保存期間Cookieの有効期限などで決まるサーバー側のタイムアウトや無効化などで決まる

Cookieは「名前と値」を保存する小さなデータです。サーバーがレスポンスで保存を指示すると、ブラウザはそのCookieを保存し、送信先の条件に合うリクエストに付けます。どのサイトへでも送られるわけではありません。ドメインやパスなどの条件があります。

セッションのデータはサーバー側に置きます。Cookieでセッションを識別する構成では、ブラウザが送るのは、その保存先を区別するセッションIDです。サーバー側に保存した値が、全部Cookieへコピーされるわけではありません。

ここまで説明したのは、CookieへセッションIDを保存する使い方です。Cookieには、displayMode=compactのような表示設定そのものを保存する使い方もあります。設定値を直接渡すCookieと、セッションIDを渡すCookieでは役割が異なります。同じアプリで両方を使うこともできます。

表示設定なら、Cookieへ値を保存する方法が使える

タスク一覧で「標準表示」と「コンパクト表示」を選べるとします。コンパクト表示では行の余白を小さくして、同じ画面に多くのタスクを表示します。ここでは、そのブラウザで選んだ表示形式を覚える例にします。

Cookieの値今回選ぶ表示
displayMode=compactコンパクト表示
Cookieがない、またはcompact以外標準表示

この設定が消えても、標準表示へ戻せば一覧は使えます。また、利用者がcompactをstandardへ変更しても、他の人の情報が見えるような権限の変更は起こしません。こうした表示上の好みは、Cookieへ保存する候補になります。

ただし、Cookieの設定はそのブラウザ側にあります。別の端末でも同じ設定を使いたいなら、利用者の設定としてDBへ保存する設計も考えられます。「表示設定は必ずCookie」と決まっているわけではありません。

Javaで表示設定をCookieへ保存する

次は、コンパクト表示が選ばれたとき、設定を受け取るServletのdoPost内で使うコードです。Cookieクラスはimport jakarta.servlet.http.Cookie;で読み込みます。フォームや一覧のHTML全体ではなく、保存に関係する部分を取り出しています。

Cookie cookie = new Cookie("displayMode", "compact");
cookie.setMaxAge(30 * 24 * 60 * 60);
cookie.setPath(request.getContextPath() + "/");
cookie.setHttpOnly(true);
response.addCookie(cookie);
  • new Cookie:displayModeという名前とcompactという値を用意する。
  • setMaxAge:Cookieの有効期間を秒で指定する。この例は30日間。
  • setPath:このアプリのパス配下で送るCookieにする。
  • setHttpOnly(true):ページ内のJavaScriptから、このCookieを直接読み取れないようにする。
  • response.addCookie:レスポンスにCookieの保存指示を追加する。

アプリのパスが/learning-appなら、setPathに指定する値は/learning-app/です。アプリをルートで動かしている場合は/になります。ここではDomainを指定せず、保存指示を出したホスト向けのCookieを使います。

new Cookieでオブジェクトを作るだけでは、ブラウザへ保存されません。addCookieによってレスポンスへ指示を載せ、ブラウザが受け取って保存します。HTTPヘッダーの名前では、保存指示がSet-Cookie、次のリクエストでの送信がCookieです。

なお、addCookieを呼んだ直後に、同じリクエストのgetCookiesへ新しい値が追加されるわけではありません。getCookiesが読むのは、そのリクエストをブラウザが送った時点のCookieです。新しい値は、ブラウザが保存した後のリクエストで送られます。

次のリクエストでCookieを取り出し、値を確認する

一覧を表示するServletのdoGet内では、届いたCookieからdisplayModeを探します。最初は標準表示とし、compactが届いた場合だけコンパクト表示へ切り替える例です。

String displayMode = "standard";
Cookie[] cookies = request.getCookies();

if (cookies != null) {
    for (Cookie cookie : cookies) {
        if ("displayMode".equals(cookie.getName())) {
            if ("compact".equals(cookie.getValue())) {
                displayMode = "compact";
            }
            break;
        }
    }
}

getCookiesの戻り値はCookieの配列です。Cookieが届いていない場合はnullなので、先に確認します。配列には他のCookieも入るため、getNameで目的の名前を探し、getValueで値を読みます。

処理後のdisplayModeは、standardかcompactのどちらかです。この値をJSPなどの表示処理へ渡し、表示形式を選びます。上のコードだけで行の余白が変わるわけではありません。

もしdisplayMode=unknownが送られても、そのまま採用せずstandardを使います。サーバーが以前Cookieを設定したとしても、戻ってきた値が変更されていないとは限りません。使ってよい値を決めてから受け入れることが大切です。

ログイン情報は、Cookieの値だけで判断しない

たとえば、CookieへloginUserId=1001と書いて、それが届いたら「利用者1001としてログイン済み」と判断する実装は危険です。利用者が値を1002へ変えれば、他の人として扱われてしまうためです。

HttpSessionを使う構成では、サーバーでIDとパスワードなどを確認し、認証に成功した結果をセッションへ保存します。次のリクエストでは、セッションIDに対応する保存先からその情報を読みます。保存・取得のコードは、Javaのセッションとは?画面を移動してもログイン状態が続く仕組みで説明しています。

ブラウザから届いた利用者IDやisAdmin=trueといった値を、そのままログイン・権限の根拠にしてはいけません。一方、セッションを使えば、何もしなくても安全というわけでもありません。有効なセッションIDを盗まれると、ログイン状態を悪用されるおそれがあります。

対策・設定何のために使うか
HTTPS通信内容が途中で読み取られたり変更されたりするリスクを減らす。
SecureCookieをHTTPSなどの安全な通信で送るよう制限する。
HttpOnlyページ内のJavaScriptからCookieを直接読み取れないようにする。
SameSite別サイトを起点としたリクエストで、Cookieを送る条件を制限する。

これらは役割が違います。HttpOnlyを付けても「利用者がCookieを書き換えられない」「CSRFを完全に防げる」という意味にはなりません。公開するアプリではHTTPSを前提にCookieの設定を確認し、ログイン時のセッションID変更や、処理ごとの権限確認も組み合わせます。上の表示設定のサンプルは、認証用Cookieを実装したものではありません。

Cookieの30日間と、セッションの30分間は別の期限

表示設定のCookieに指定した30日間は、ブラウザ側のCookieの有効期間です。アクセスするたびに自動で30日後へ延長される設定ではありません。同じCookieを再設定すれば、そこから改めて期限が設定されます。利用者による削除やブラウザの制限で、期限前に使えなくなる場合もあります。

一方、次はサーバー側のセッションを、30分間アクセスがなければ無効にする設定です。有効なHttpSessionをsession変数で取得した後に呼びます。

session.setMaxInactiveInterval(30 * 60);

こちらも単位は秒ですが、数えているものが違います。Cookieの30日間を指定しても、セッションの期限が30日間へ変わるわけではありません。

時刻操作・状態
10:00表示設定Cookieを30日間で保存。ログイン後のセッションは無操作30分に設定。
10:20同じセッションで画面を開く。セッションの最終アクセス時刻が更新される。
10:50を過ぎてからそれ以降リクエストがなければ、セッションは期限切れになる。
翌日表示設定Cookieが残っていればコンパクト表示を選べる。一方、ログインには再認証が必要になる。

この表は、表示設定のCookieを再設定せず、自動的な通信も発生していない例です。セッションの無操作時間は、単に画面を開いていた時間ではなく、そのセッションへのリクエストが来ない時間として考えます。

また、ブラウザにJSESSIONIDが残っていても、対応するサーバー側のセッションが無効なら、以前のログイン情報は取り出せません。逆に、ブラウザからJSESSIONIDを削除しただけで、サーバー側のセッションが直ちに消えるわけでもありません。Cookieの管理と、サーバー側の無効化は別です。

「セッションCookie」は、HttpSessionそのものではない

Cookieには、期限を指定して一定期間残すものと、ブラウザのセッション中だけ使うものがあります。後者をセッションCookieと呼びます。この「セッション」は、JavaのHttpSessionという保存先そのものを指す言葉ではありません。

JavaのCookieでは、setMaxAgeを省略した場合、標準値は-1です。ただし、ブラウザの復元機能でCookieが復元される場合もあるので、「ブラウザを閉じれば必ずログアウト」とは扱いません。ログアウトはサーバー側での無効化を含めて実装します。

Cookieを削除する処理と、セッションを無効にする処理

表示設定を忘れさせたい場合は、同じ名前・パスのCookieを、有効期間0で送り直します。この記事ではDomainを指定していないので、削除側でも指定していません。

Cookie cookie = new Cookie("displayMode", "");
cookie.setPath(request.getContextPath() + "/");
cookie.setMaxAge(0);
response.addCookie(cookie);

空文字列を入れたことではなく、setMaxAge(0)で削除を指示している点が重要です。ブラウザが指示を受け取った後、次のリクエストでdisplayModeが送られなければ、先ほどの取得コードは標準表示を選びます。この操作では、ログイン用のセッションは無効になりません。

反対に、ログアウトなどでサーバーのセッションを無効にするのは、次の処理です。

HttpSession session = request.getSession(false);
if (session != null) {
    session.invalidate();
}

この処理はセッションを無効にしますが、無関係なdisplayModeのCookieまで削除するものではありません。ログアウト後もコンパクト表示が残っていて構わないのであれば、表示設定とログイン状態を別々に管理することで、その動作を実現できます。

どちらに保存するかは「用途」で決める

  • このブラウザでの表示設定を覚えたい:Cookieへ設定値を保存する方法を検討する。受け取る値は確認する。
  • ログイン成功後の情報を複数のリクエストで使いたい:認証済みの情報をサーバー側のセッションへ保存する。CookieにはセッションIDを持たせる構成にできる。
  • 今回のJSPにだけ値を渡したい:request属性で足りるかを考える。何でもセッションへ入れない。
  • 別端末でも共有したい・長く確実に記録したい:DBなどへの保存を考える。Cookieやセッションだけに頼らない。

確認問題

  1. 記事の例で、displayMode=compact、JSESSIONID=A1、loginUserId=1001は、それぞれどこに保存されていますか。次の画面でも同じセッションを使える理由と、前のリクエストスコープの情報がそのまま使えるかを説明してください。
  2. CookieにloginUserId=1001を保存し、その値が届けば利用者1001としてログイン済みと判断する実装には、どんな問題がありますか。表示設定のCookieも含め、受け取った値をどう扱うべきか答えてください。
  3. 10:00に表示設定Cookieを30日間で保存し、セッションは無操作30分に設定しました。10:20に同じセッションへアクセスし、その後は翌日まで通信しませんでした。翌日にコンパクト表示は残っているのに、再ログインを求められることはありますか。理由も答えてください。
  4. displayModeを削除したいとき、setMaxAgeには何を指定し、パスはどうしますか。このCookieの削除とsession.invalidate()は、互いに同じ処理ですか。

演習後の確認:こちらのページを使って、自分で考えた内容を見直せます。

まとめ

  • リクエストスコープもセッションスコープも、サーバー側の情報置き場。前者は1回のリクエスト、後者は同じ有効なセッションに対応する複数のリクエストで使う。
  • Cookieに保存したセッションIDをブラウザが送ることで、サーバーは前回と同じセッションを見つける。
  • Cookieには表示設定などの値そのものを保存することもできる。受け取った値は、変更される可能性を考えて確認する。
  • Cookieの有効期間と、セッションの無操作タイムアウトは別々に管理する。
  • 表示設定・認証済み情報・永続的な記録など、用途から保存先を選ぶ。

関連記事

シェアする