Skip to main content

セッションとログインの仕組み

この章では、Webアプリケーションにおける セッション(Session) の役割を理解し、 Springを使ってログイン状態の保持や、買い物カート機能などを自力で実装できるようにする。

Webアプリでは、ページを移動してもユーザー情報を保持する必要がある。 たとえば「ログイン中のユーザ情報」や「非ログインユーザのカートに入れた商品一覧」など、リクエストをまたいで継続的に利用するデータ を扱うためにセッションが使われる。

この章では、セッションの仕組みを自分の手で実装して理解することを目的とする。

学習のゴール

  • セッションとは「ブラウザごとに保持される一時的なサーバー側の保存領域」であることを理解する
  • リクエストスコープとセッションスコープの違い、およびその使い分けを説明できる
  • Springで HttpSession@SessionAttributes@SessionAttribute を使ってデータを保持・取得できる
  • セッションを利用して、ログイン状態やカート情報を保持する仕組みを実装できる
  • コントローラとビューで同じキー名を使ってデータをやり取りできる
  • セッションに保存してよい情報・保存すべきでない情報を区別できる
  • セッションのタイムアウトや破棄の仕組みを理解し、安全に扱えるようになる

スコープとは何か

Spring MVCでは、オブジェクトが「どの範囲(スコープ)」で有効かを制御できる。 スコープとは、「そのデータがどのくらいの期間・範囲で保持されるか」を表す考え方である。

スコープ名有効期間主な用途
リクエストスコープ1回のリクエスト(ページ表示の1回分)フォーム送信やページ遷移時の一時データ
セッションスコープブラウザを閉じるまで(または明示的に削除するまで)ログイン情報、買い物カートなど「ユーザーごとに保持する情報」

リクエストスコープとセッションスコープの違い

リクエストスコープでは、ページを移動するたびに情報が消える。

  1. /login にアクセス → コントローラが name="Taro" を保存(リクエストスコープ)
  2. /home にアクセス → 別リクエストになるため、"Taro" は消える

一方で、セッションスコープではブラウザごとに情報が保持される。

  1. /login にアクセス → "Taro" をセッションに保存
  2. /home にアクセス → 別リクエストでもセッションに保存された "Taro" を参照できる

👉 つまり、セッションは「ユーザーごとに長く使われる入れ物」 である。 ログイン状態やカート情報など、ページをまたいで保持すべきデータはセッションに保存する。


Springでセッションを扱う方法

Spring MVCでは、セッションを直接扱うための HttpSession と、 Spring独自の方法でセッションを管理する @SessionAttributes という2つの方法がある。

それぞれの使い方と、どんな場合にどちらを使うべきかを見ていこう。


方法1:HttpSession を使う(最も基本的でわかりやすい方法)

HttpSessionServlet(サーブレット)という仕組み にもともと用意されている標準的なクラスで、 Spring Bootでもそのまま利用できる。

セッションに値を保存・取得する処理を、自分で書くイメージ。

@Controller
public class LoginController {
@PostMapping("/login")
public String login(@RequestParam String username, HttpSession session) {
// セッションに値を保存
session.setAttribute("loginUser", username);
return "redirect:/home";
}
@GetMapping("/home")
public String home(HttpSession session, Model model) {
// セッションから値を取り出す
String user = (String) session.getAttribute("loginUser");
model.addAttribute("user", user);
return "home";
}
}
  • session.setAttribute("loginUser", username)"loginUser" というキーで値をセッションに保存
  • session.getAttribute("loginUser") → 同じキーで値を取り出す
  • このキー名(文字列)はコントローラとビューで一致している必要がある

ビュー例:

home.html
<h1>ようこそ!</h1>
<p>[[${session.loginUser}]] さんとしてログイン中です。</p>
<a href="/logout">ログアウト</a>
  • [[${session.loginUser}]] はセッション内の "loginUser" にアクセスして表示している

特徴と使いどころ

  • ✅ すべてのコントローラから同じセッション情報にアクセスできる
  • ✅ ログイン状態やショッピングカートなど、アプリ全体で共有したい情報 に向いている
  • ❌ 手動で setAttribute / getAttribute を呼ぶ必要がある
  • ❌ 大量に使うとキーの管理が煩雑になる

方法2:@SessionAttributes を使う(Spring流のセッション管理)

@SessionAttributes「Model(ビューへ渡す値)」のうち、特定の名前をセッションにも保持してくれる 仕組み。 Springが自動でセッションにコピーしてくれるので、コードがすっきりする。

@Controller
@SessionAttributes("loginUser") // ← "loginUser"というModel属性をセッションにも保存
public class LoginController {
@PostMapping("/login")
public String login(@RequestParam String username, Model model) {
// Modelに追加した値が、自動的にセッションにもコピーされる
model.addAttribute("loginUser", username);
return "redirect:/home";
}
@GetMapping("/home")
public String home(
@ModelAttribute("loginUser") String user // ← セッションから自動的に取得
) {
return "home";
}
@GetMapping("/logout")
public String logout(SessionStatus status) {
// セッションに保持している@SessionAttributesの情報を破棄
status.setComplete();
return "redirect:/login";
}
}

特徴

  • @SessionAttributes("loginUser") と書いた名前(キー)だけが自動でセッションに保存される
  • @ModelAttribute("loginUser") と書くと、セッションから自動的に値を取得できる
  • SessionStatus#setComplete() を呼ぶと、このコントローラのセッション情報だけを破棄できる
  • ⚠️ この仕組みは、そのコントローラの中でしか有効にならない
    • つまり、別のコントローラではこのセッション属性を自動では参照できない
    • (ただし、HttpSession から手動で取り出すことは可能)

補足:@SessionAttribute(最後に s がつかない)との違い

名前が似ているが、全く別のもの。

  • @SessionAttributes(複数形) … Model属性をセッションに「保存」する ためのアノテーション
  • @SessionAttribute(単数形) … すでにセッションに保存済みの値を「取り出す」 ためのアノテーション

たとえば、先ほどの "loginUser"HttpSession で保存している場合、 別のコントローラでこう書けば簡単に取り出せる。

@GetMapping("/mypage")
public String myPage(@SessionAttribute("loginUser") String user, Model model) {
model.addAttribute("user", user);
return "mypage";
}

@SessionAttributesHttpSession の違いまとめ

比較項目HttpSession@SessionAttributes
範囲アプリ全体(全コントローラで共有)コントローラ単位
保存方法手動 (setAttribute)自動(model.addAttribute
取得方法手動 (getAttribute)自動(@ModelAttribute
破棄方法session.invalidate()SessionStatus#setComplete()
主な用途ログイン情報・カートなど長期保持入力画面の途中保持・一時的な値
学習レベル感基本(Servlet標準)Spring的な応用

使い分けの目安

ケース適した方法
ログイン状態の保持HttpSession
ショッピングカートの保持HttpSession
入力フォームの途中保持(確認画面→完了画面など)@SessionAttributes
すでにセッションにある値を簡単に読み取りたいだけ@SessionAttribute

まとめ

  • HttpSession … 最も基本でどのコントローラからも使える
  • @SessionAttributes … コントローラ単位でSpringが自動管理するセッション(Modelとの橋渡し)
  • @SessionAttribute … 既に保存されたセッション値を取り出すときに使う

ログイン状態やカート情報のような「全体で共有する情報」は HttpSession、 画面遷移中の一時的な入力情報などは @SessionAttributes を使うのが基本である。


セッションを使った買い物カートの例

セッションを利用すれば、ログイン情報だけでなく、買い物カートのように 「ログイン前のユーザーがページをまたいでも保持される情報」 を扱うこともできる。

CartController.javajava
package com.example.demo.controller;
import jakarta.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
@Controller
public class CartController {
@GetMapping("/cart")
public String viewCart(HttpSession session, Model model) {
List<String> cart = (List<String>) session.getAttribute("cart");
if (cart == null) {
cart = new ArrayList<>();
session.setAttribute("cart", cart);
}
model.addAttribute("cart", cart);
return "cart";
}
@PostMapping("/cart/add")
public String addToCart(@RequestParam String item, HttpSession session) {
List<String> cart = (List<String>) session.getAttribute("cart");
if (cart == null) {
cart = new ArrayList<>();
}
cart.add(item);
session.setAttribute("cart", cart);
return "redirect:/cart";
}
@GetMapping("/cart/clear")
public String clearCart(HttpSession session) {
session.removeAttribute("cart");
return "redirect:/cart";
}
}

ビュー例

cart.html
<h2>ショッピングカート</h2>
<form action="/cart/add" method="post">
<input type="text" name="item" placeholder="商品名">
<button type="submit">追加</button>
</form>

<ul>
<li th:each="item : ${cart}">[[${item}]]</li>
</ul>

<a href="/cart/clear">カートを空にする</a>
  • セッションキー "cart" を通じて、リクエストをまたいでもカート内容が保持される
  • ユーザーごとに異なるセッションが作られるため、他のユーザーのカートと混ざらない

セッションを使う際の注意点

セッションは便利な仕組みだが、使い方を誤るとパフォーマンス低下やセキュリティ上の問題を引き起こすことがある。 そのため、以下のような注意点を理解しておくことが重要である。


1. 保存しすぎに注意(メモリを圧迫する)

セッションはサーバー側のメモリに保存されるため、大量のデータを入れると負荷がかかる。

  • ✅ セッションに入れるのは「必要最小限の情報」にとどめる 例:ユーザーIDや名前など、画面間で共通して使う小さなデータ
  • ❌ 大量のリスト・画像・検索結果などをセッションに保存しない
  • 💡 一時的に使う大量データは、リクエストスコープやDB側に保存する

2. 個人情報の取り扱いに注意

セッションはユーザーごとの情報を保持するため、個人情報を直接保存しない ことが望ましい。

  • ✅ IDや参照キーだけを保持し、詳細情報は必要なときにDBから取得する
  • ❌ 住所やメールアドレスなど、個人情報そのものをセッションに保持しない

3. タイムアウトに注意

多くのWebアプリでは、一定時間操作がないとセッションが切れる(タイムアウト)。 これはセキュリティ対策として重要だが、切れた後の動作も設計しておく必要がある。

  • Spring Bootではデフォルトで 30分 が有効期限
  • application.properties で変更できる
server.servlet.session.timeout=20m
  • タイムアウト後は session.getAttribute() で値を取得できなくなる
  • ログイン状態をセッションで保持している場合、ログイン画面へリダイレクトさせる処理を実装する
セッションタイムアウトの設定

application.propertiesserver.servlet.session.timeout を設定するとき、 単位は秒(600)または 20m(20分)/ 1h(1時間)のように書ける。

ログインセッションは長すぎず短すぎず適切な値を設定することが重要である。


4. ログアウト時は必ずセッションを破棄する

ログアウト後にセッションを残したままにすると、別ユーザーが同じブラウザを使った場合に前の情報が残る危険がある。

  • ✅ ログアウト処理で session.invalidate() を呼び出す
  • ✅ セキュリティ上重要なデータは、ログアウト前に明示的に削除してもよい

5. 複数タブの扱いに注意

セッションは「ブラウザごと」に共有される。 そのため、同じブラウザで複数タブを開くとセッション内容も共有される

  • カート機能やログイン情報などは問題ないが、フォーム入力の途中保存などは競合の原因になる
  • 画面ごとに別の情報を持たせたい場合は、リクエストスコープやトークンを併用する

6. セッション固定攻撃(Session Fixation)への対策

セッション固定攻撃(Session Fixation)に注意

攻撃者があらかじめセッションIDを固定し、被害者が同じIDでログインすることで 不正にそのセッションを乗っ取る手法 をセッション固定攻撃と呼ぶ。

対策として、ログイン成功後にセッションIDを再発行する ことが必須である。

// ログイン時にセッションIDを再発行する
session.invalidate(); // 古いセッションを破棄
HttpSession newSession = request.getSession(true); // 新しいセッションを発行
newSession.setAttribute("loginUser", username); // 新セッションに情報をセット

Spring Security を使う場合はこの対策が自動で行われるが、自前でログイン処理を実装する場合は必ず上記の処理を行うこと。

7. 分散環境ではセッションの扱いに注意(上級)

複数サーバーで構成されるシステムでは、セッションをどのサーバーで保持するか を考慮する必要がある。 ただし、初学者の段階では「セッションはサーバー側で保持される仕組み」だけ理解しておけば十分。


よくある質問

Q. セッションとCookieの違いは何ですか?

A. セッション情報は サーバー側 に保存される。 ブラウザ(クライアント)には「セッションID」という識別子だけがCookieに保存される。 ブラウザはリクエストのたびにCookieにセッションIDを添付して送ることで、サーバーはどのセッションかを特定できる。


Q. シークレットモード(プライベートブラウジング)を使うとセッションはどうなりますか?

A. シークレットモードでは通常ブラウジングと 別のセッション が作られる。 ログイン状態やカート情報は引き継がれず、新規セッションとして扱われる。 また、シークレットモードを閉じるとCookieが削除されるため、セッションにアクセスできなくなる。


Q. ログアウト処理はどのように実装しますか?

A. session.invalidate() を呼ぶとセッション全体を破棄できる。

@GetMapping("/logout")
public String logout(HttpSession session) {
session.invalidate(); // セッションを破棄
return "redirect:/login";
}

Q. セッションに保存したオブジェクトのクラスを後から変更したらどうなりますか?

A. セッションに保存したオブジェクトをデシリアライズ(復元)できなくなる場合がある。 これはアプリを再起動せずにセッションが残っているケースで起こりやすい。 開発中にクラス構造を変更した場合は、セッションを一度無効化してから再テストすることを推奨する。


まとめ

  • リクエストスコープ は1回限りの通信で有効、セッションスコープ はユーザー単位で継続的に有効
  • セッションを使うと、ログイン状態やカートの中身などをリクエストをまたいで保持できる
  • Springでは HttpSession@SessionAttributes の2通りの方法でセッションを扱える
    • HttpSession:アプリ全体で共通利用(ログイン情報・カートなどに最適)
    • @SessionAttributes:コントローラ単位での一時的な情報保持(入力フォームなどに最適)
  • セッションに保存する値は、コントローラとビューで キー名(例:"loginUser")を一致させる必要がある
  • ログアウト時やタイムアウト時には session.invalidate() などで必ず破棄する
  • 不要なデータや個人情報を入れすぎないようにし、セッションは軽く・安全に保つ のが基本である
  • 自前でログイン処理を実装する場合は、セッション固定攻撃への対策として ログイン後にセッションIDを再発行する こと

Webアプリケーションにおける「セッション」の説明として、最も適切なものを選べ。

正解

D. HTTPのステートレス性を補うために、サーバー側でユーザーごとの状態を保持する仕組みのこと

解説

HTTPはステートレスなプロトコルであり、リクエストとリクエストの間に状態を保持しない。

セッションはこの問題を解決するため、サーバー側でユーザーごとの状態を保持する仕組みである。

仕組み:

  1. ユーザーが初めてアクセスすると、サーバーがセッションIDを発行する
  2. セッションIDはCookieとしてブラウザに保存される
  3. 以降のリクエストでCookieにセッションIDが付加され、サーバーがユーザーを識別する

典型的な用途:ログイン状態の保持、ショッピングカート

セッションスコープとリクエストスコープの違いとして、正しいものを選べ。

正解

B. リクエストスコープは1つのHTTPリクエストの間だけデータが保持され、セッションスコープはユーザーのブラウザセッション中データが保持される

解説

スコープとはデータが有効な範囲のことである。

スコープ有効期間用途の例
リクエストスコープ1回のHTTPリクエストの間検索結果の表示、エラーメッセージ
セッションスコープユーザーのブラウザセッション中ログイン情報、ショッピングカート
アプリケーションスコープアプリ起動中ずっとカウンター、共有設定

ログイン情報のように複数のリクエストをまたいで保持したいデータにはセッションを使う。

SpringMVCにおける HttpSession オブジェクトの役割として、最も適切なものを選べ。

正解

A. Javaのコードからセッションにデータを保存・取得・削除するためのインターフェース

解説

HttpSession はJavaServlet APIで提供されるセッション管理用のインターフェースである。

主なメソッド:

  • session.setAttribute("key", value): セッションにデータを保存
  • session.getAttribute("key"): セッションからデータを取得
  • session.removeAttribute("key"): セッションからデータを削除
  • session.invalidate(): セッションを無効化(ログアウト時に使う)

Springのコントローラメソッドの引数に HttpSession session と記述するだけで使える。

ログインしたユーザー情報をセッションに保存するコードを完成させよ。

@PostMapping("/login") public String login(LoginForm form, HttpSession session) { User user = userService.authenticate(form);
("loginUser", user); return "redirect:/home"; }

解答例
session.setAttribute("loginUser", user);
解説

session.setAttribute("key", value) でセッションにデータを保存する。

保存できる値はシリアライズ可能なJavaオブジェクトならすべて対応している。

セッションデータはサーバー側のメモリに保存されるため、大きなオブジェクトを大量に保存するとメモリを圧迫する点に注意する。

セッションからログインユーザー情報を取得するコードを完成させよ。

@GetMapping("/home") public String home(HttpSession session, Model model) { User loginUser =
("loginUser"); model.addAttribute("user", loginUser); return "home"; }

解答例
User loginUser = (User) session.getAttribute("loginUser");
解説

session.getAttribute("key") はセッションから値を取得する。

戻り値の型は Object なのでキャスト(型変換)が必要である。

セッションに値が存在しない場合は null が返るため、null チェックを行うことが重要である。

セッションスコープとリクエストスコープの使い分けとして、最も適切なものを選べ。

正解

C. ログイン後の認証状態のようにリクエストをまたぐデータにはセッションを使い、1リクエスト内の一時的なデータにはリクエストスコープを使うのが適切である

解説

スコープの適切な使い分け:

**リクエストスコープ(Model)**を使う場面:

  • 検索結果の表示
  • バリデーションエラーメッセージ
  • フラッシュメッセージ(一度だけ表示するメッセージ)

セッションスコープを使う場面:

  • ログイン後の認証情報
  • ショッピングカートの内容
  • 多段階フォームの途中データ

スコープを適切に使い分けることで、不必要なデータをセッションに保持するメモリの浪費を防げる。

orderForm という名前のモデル属性をセッションに保持するコントローラを完成させよ。

@Controller
public class OrderController { // 多段階フォームの処理 }

解答例
@Controller @SessionAttributes("orderForm") public class OrderController { }
解説

@SessionAttributes("属性名") をコントローラクラスに付けると、指定した名前のモデル属性がセッションに自動保存される。

複数ステップのフォーム(ウィザード)でフォームデータを保持するのに使う。

セッションのクリアには SessionStatus.setComplete() を呼び出す。

@PostMapping("/complete")
public String complete(SessionStatus status) {
    status.setComplete(); // セッションから@SessionAttributesのデータを削除
    return "order/complete";
}

フォーム完了後にセッションデータを破棄するメソッドを完成させよ。

@PostMapping("/complete") public String complete(
status) {
; return "order/complete"; }

解答例
public String complete(SessionStatus status) { status.setComplete(); return "order/complete"; }
解説

SessionStatus.setComplete() を呼び出すことで、@SessionAttributes で保持していたセッションデータを削除できる。

多段階フォームの完了時に呼び出すことで、不要なセッションデータをクリアすることが重要である。

注意:session.invalidate() はセッション全体を破棄するが、status.setComplete()@SessionAttributes で指定した属性のみを削除する。

ログイン処理とログアウト処理を完成させよ。

ログイン

User user = userService.authenticate(form);
; return "redirect:/home";

ログアウト

; return "redirect:/login";

解答例
session.setAttribute("loginUser", user); // ログアウト時 session.invalidate();
解説

セッションを使ったログイン機能の実装パターン:

ログイン処理:

  1. フォームの入力値でユーザーを検索する
  2. パスワードを検証する
  3. session.setAttribute("loginUser", user) でセッションに保存する

ログアウト処理:

  • session.invalidate() でセッション全体を無効化する

ログインチェック:

  • session.getAttribute("loginUser") == null の場合は未ログイン

セッションを使って商品をカートに追加するコードを完成させよ。

@PostMapping("/cart/add") public String addToCart(Long productId, HttpSession session) { List<CartItem> cart =
; if (cart == null) { cart = new ArrayList<>(); } cart.add(new CartItem(productId));
; return "redirect:/cart"; }

解答例
List<CartItem> cart = (List<CartItem>) session.getAttribute("cart"); if (cart == null) { cart = new ArrayList<>(); } cart.add(new CartItem(productId)); session.setAttribute("cart", cart);
解説

セッションを使ったカート機能の実装パターン:

  1. セッションからカートを取得する(初回は null
  2. null の場合は新しいリストを生成する
  3. 商品を追加する
  4. 更新したカートをセッションに保存する

このパターンはカート以外にも「複数リクエストにまたがってデータを蓄積する」場面で活用できる。