前回は「フォームからデータを送る」方法を学びました。リクエストとレスポンスの仕組みが理解できたのではないでしょうか?
今回は、今までの知識を応用してログイン処理を作ってみます。
これまでの知識で入力画面に入力した内容をもとにThymeleafの出力画面に情報を表示できるようになりました。しかし、複数ページを跨いで情報の保持はできていません。ステートレスといって1対のリクエストとレスポンスで終了してしまうのがhttpプロトコルの基本です。
そこで必要となるのがセッション属性です。セッション属性を使うことで複数ページを跨いで情報の保持ができます。そしてログイン処理は複数ページを跨いで情報を保持する必要のある典型的な処理です。この章ではそんなセッション属性の特徴をログイン処理を実装することにより体験的に学びます。

1. ログイン処理とは
まずは一般的なログイン処理についてまとめておきましょう。皆さんも是非、普段お使いのWebアプリケーションで実験してみてください。(あるいは講師の実演をご覧ください)
突然ですがログインに関する質問です。
- ①一般にWebシステムにログインするにはどのような情報が必要ですか?
あなたの答え: |
- ②ログイン前と後では何が違いますか?(ログインすると何が良いのですか?)
あなたの答え: |
- ③あるブラウザでログイン中に同じブラウザで他のタブを開いたら新しい方のタブでログインは継続されますか?
あなたの答え: |
- ④あるブラウザ(例えばクローム)でログイン中に新しいブラウザ(例えばエッジ)を起動したら新しいブラウザでログインは継続されますか?
あなたの答え: |
- ⑤どんなときにWebアプリからログアウトしますか?
あなたの答え: |
上記の実験からも明らかなように一般的にはログインとは以下のような処理を指します。
①ユーザーはブラウザからIDとパスワードといった自分だけが知る情報を使ってログインする
②ログイン後はそのユーザーだけが見られるページに遷移できるようになる
③ログイン中は同じブラウザで他のタブを開いてもログインされたままである
④しかし、同じユーザーが別のブラウザで同じページにアクセスしても再度ログイン処理が必要である
⑤ログアウト処理をする、ブラウザを終了するまたは一定時間(大抵は30分間だが銀行のように5分程度と短い場合も)が経過するとログアウトする
上記結果④から考えるとログイン処理はブラウザごとに固有の情報を利用していると推測できます。
また、結果⑤から考えるとログアウトのタイミングは技術者が個々に設定していることが分かります。
本章では、ログイン処理を実現するにはセッションという仕組みを理解することが必要になるということ、さらに同一セッションで有効なセッション属性を使うことで複数リクエストを跨いだデータ保持が可能になるということを解説していきたいと思います。
例えば、オンラインショッピングで買い物かごの仕組みが実現できるのは、かごの中身をこのセッション属性に入れているから複数ページに渡ってデータを保持できるのです。次の商品を見に行くたびに買い物かごの中身がクリアされてしまっては使い物になりませんからね。
セッション属性とは開始してから破棄されるまで同一ブラウザの同一セッションで有効なスコープです。
セッションの破棄は、「ログアウト処理」、「ブラウザを終了する」または「セッションタイムアウト」により実行されます。
実は、前章までの知識で実現できるのは上記「①ユーザーはブラウザからIDとパスワードといった自分だけが知る情報を使ってログインする」だけです。
ここからはセッションを使って「②~⑤の機能」をどのように実装するかを見ていきましょう。
前章までの知識では「① ブラウザからIDとパスワードを入力して送信する」ことまでしかできませんでしたが、セッションを利用することで「②ログイン後はユーザー専用のページへ行けるようになる」「③同じブラウザの別タブでもログイン状態が継続される」「④別ブラウザからアクセスしたら再度ログインが必要になる」「⑤ログアウト、またはタイムアウトなどでセッションが破棄される」機能を実装できます。
2. ログイン処理の実装
2.1 セッションを使う基本的な流れ
一般的に、HttpSession
を使ったログインの流れは次のようになります。
- ログイン画面からIDとパスワードを送信
- ユーザーはブラウザ上でフォームにIDとパスワードを入力し、サーバへ送信します。
- サーバ側で認証処理(ユーザーの正当性チェック)
- コントローラや認証クラスなどで、送られてきたIDとパスワードが正しいかを確認します。実際のシステムではデータベースや外部サービスを参照することが多いでしょう。
- 認証が成功したら
HttpSession
にユーザー情報を設定session.setAttribute("userId", ユーザーIDなど)
のように、ログインしたユーザーを一意に識別できる情報をセッションへ格納します。
- セッションが有効な間はログイン状態が保持される
- 次の画面遷移以降、コントローラで
session.getAttribute("userId")
を取得して、ユーザーがログイン中かどうかを判定します。
- 次の画面遷移以降、コントローラで
- ログアウトまたはセッション破棄で終了
- ユーザーが明示的にログアウトするか、ブラウザを閉じる、一定時間操作がない(タイムアウト)などでセッションが破棄されると、ログイン状態は失われます。
2.2 実装例
以下はシンプルなログイン画面とログイン後の画面の例です。もちろん、実際のシステムでは認証処理をデータベースや認証サーバと連携させることになりますが、ここでは学習用に固定文字列(ユーザーIDは"imai"、パスワードは"p")でチェックする簡易実装としています。

コントローラの例
以下のサンプルコードでは、ユーザーIDが imai
、パスワードが p
の場合のみログイン成功とみなし、セッションへユーザー情報を保存する例を示します。
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import jakarta.servlet.http.HttpSession;
@Controller
public class LoginController {
// ログイン画面を表示(GET)
@GetMapping("/login")
public String showLoginForm() {
return "login"; // login.html を返す
}
// ログイン処理(POST)
@PostMapping("/login")
public String doLogin(
@RequestParam("userId") String userId,
@RequestParam("password") String password,
HttpSession session
) {
// ユーザーIDとパスワードを判定
if ("imai".equals(userId) && "p".equals(password)) {
// ログイン成功 → セッションにユーザーIDを保存
session.setAttribute("userId", userId);
// 直接 home.html を表示
return "home";
} else {
// ログイン失敗 → 再度ログイン画面へ
return "login";
}
}
// ログイン後のホーム画面(GET)
@GetMapping("/home")
public String showHome(HttpSession session) {
// セッションからユーザー情報を取得
String loginUser = (String) session.getAttribute("userId");
if (loginUser == null) {
// 未ログインの場合、ログイン画面へリダイレクト
return "redirect:/login";
}
return "home"; // video.html を表示
}
// ログアウト処理(POST)
@PostMapping("/logout")
public String logout(HttpSession session) {
// セッションを無効化
session.invalidate();
// ログイン画面にリダイレクト
return "redirect:/login";
}
}
@RequestParam("userId") String userId
でフォームから送られてきたuserId
を直接受け取ります。"imai".equals(userId) && "p".equals(password)
でログイン判定(本来はデータベースなどで認証しますが、ここでは固定文字列で簡易化)。session.setAttribute("userId", userId)
でログインに成功したユーザーをセッションに保存。session.invalidate()
でセッション全体を破棄し、ログアウト状態に戻します。- リダイレクトが通常のreturnとどう違うのかは後述します。
ログイン画面の例
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>ログイン</title>
</head>
<body>
<h1>ログイン画面</h1>
<!--
/login へ POST リクエストを送信し、
コントローラの doLogin(...) メソッドが呼ばれます
-->
<form th:action="@{/login}" method="post">
<div>
<label for="userId">ユーザーID:</label>
<input type="text" id="userId" name="userId" />
</div>
<div>
<label for="password">パスワード:</label>
<input type="password" id="password" name="password" />
</div>
<button type="submit">ログイン</button>
</form>
</body>
</html>
フォームの name="userId"
・name="password"
とコントローラの @RequestParam("userId")
・@RequestParam("password")
が対応している点がポイントです。
ホーム画面の例
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>ホーム</title>
</head>
<body>
<h1>ホーム画面</h1>
<!-- session から直接 loginUser を取得 -->
<p>ログインユーザー:<span th:text="${session.userId}"></span></p>
<!-- /logout へ POSTリクエストでログアウト -->
<form th:action="@{/logout}" method="post">
<button type="submit">ログアウト</button>
</form>
<p><a th:href="@{/video}">ビデオを見る</a></p>
</body>
</html>
- コントローラで
model.addAttribute("userId", loginUser)
した値をth:text="${session.userId}"
で表示しています。 - Modelに入れられた値を表示する場合は単に
th:text="${userId}"
で良かったですが、セッションに入れられた値を表示する場合にはth:text="${session.userId}"
のようにセッションから値を取得することを明示しなければなりません。
ログイン後にセッション情報を利用する例
ログイン後は会員だけのビデオページが見られるようにしています。
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import jakarta.servlet.http.HttpSession;
@Controller
public class MemberController {
@GetMapping("/video")
public String showVideoPage(HttpSession session, Model model) {
// セッションからユーザー情報を取得
String loginUser = (String) session.getAttribute("loginUser");
if (loginUser == null) {
// 未ログイン → ログイン画面にリダイレクト
return "redirect:/login";
}
// ログインしているユーザーIDを画面表示用にModelへセット
model.addAttribute("userId", loginUser);
return "video"; // video.html というテンプレートを返す
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>ビデオ</title>
</head>
<body>
<h1>ビデオ画面</h1>
<p>こんにちは<span th:text="${userId}"></span>さんビデオを楽しんでください。</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/N4muWZJ4yt8?si=OkOxQUFdpwssWWMM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</body>
</form>
</body>
</html>
ここまでのまとめ
- ホーム画面 → ビデオ画面 → ホーム画面→ ログアウト → ログイン画面 の流れ。
- セッション (
session.getAttribute("userId")
) を使ってログイン状態を管理。 - 未ログイン時に
/video
にアクセスすると/login
にリダイレクトされる。
3. HttpSession
の基本操作のまとめ
HttpSession
は HttpServletRequest
から取得し、セッションにデータを格納・取得・削除できます。
3.1 セッションの取得
HttpSession session = request.getSession();
getSession()
は、既存のセッションがあれば取得し、なければ新しく作成します。
すでにセッションがある場合は、同じセッションが取得されます。
3.2 セッションへのデータ保存
session.setAttribute("userId", "yamada");
setAttribute(String name, Object value)
でセッションにデータを保存できます。
Object
型なので、任意のオブジェクトを保存できます。
3.3 セッションからデータを取得
String user = (String) session.getAttribute("userId");
getAttribute(String name)
でデータを取得できます。
Object
型で返るため、適切な型にキャストする必要があります。
3.4 セッションからデータを削除
session.removeAttribute("userId");
removeAttribute(String name)
を使うと、セッション内のデータを削除できます。
3.5 セッションの無効化
session.invalidate();
invalidate()
を呼ぶと、セッションが破棄され、すべての属性が削除されます。
3.6 セッションのライフサイクルと注意点
- 同じブラウザの別タブ
- セッションID(クッキー)を共有しているため、同じセッションとなりログイン状態も共有されます。
- 別のブラウザでアクセス
- セッションID(クッキー)が異なるため、改めてログインが必要になります。
- セッションタイムアウト
- サーバ側に設定された時間(たとえば30分)リクエストがないと、セッションは破棄されます。
- ログアウト処理
session.invalidate()
によってログイン情報が破棄され、次回リクエスト以降はログイン状態を維持できません。
- セッションに保持する情報量
- セッションに大量のデータを保持するとサーバメモリを圧迫し、パフォーマンスが低下する可能性があるため注意します。通常は最小限のユーザーIDや権限情報だけを持たせ、詳細はDBなどを都度参照するのが一般的です。
ここまでで、HTTPがステートレスであるにもかかわらず、どのようにして「ログイン状態」を維持しているのかイメージがつかめたのではないでしょうか。
次は、上記のログイン処理で使用していたリダイレクトを解説します。
4. これまでの転送とリダイレクトの違い
項目 | これまでの転送 | リダイレクト(redirect) |
---|---|---|
用途 | 主としてControllerからView(Thymeleafなど)へ遷移するときに使う。 | 主として別のURL(同一アプリ内や外部サイト)へ遷移するときに使う。 |
ソースコードの例 | return "login"; | return "redirect:/home"; |
遷移先の指定方法 | - 相対パス(テンプレート名) (例) "login" | - 相対パス(Controller内の別のURL) (例) "redirect:/home" - 絶対URLも可(例) "redirect:https://saycon.co.jp/" |
典型的な処理の流れ | ブラウザ → Controller → View(Thymeleaf) | ブラウザ → ControllerA → ブラウザ → ControllerB |
属性 | Modelの属性を引き継ぐ | Modelの属性は渡らない セッション属性は引き継げる |
転送可能範囲 | 同一アプリケーション内のみ | 外部サーバーも可能 |
転送後のURL | URLは変化なし(ControllerのURLのまま) | 転送先のURLに変化する |
5. getメソッドとpostメソッドの違いの復習
ログイン処理では、getメソッドではなく、postメソッドを使っていました。
HTMLでも学んだ下図にあるpostメソッドの特徴をまとめました。
パラメータの格納場所 | セキュリティ | 共有 | |
get | URL 例:http://localhost:8080/login1 id=imai&pass=p | × ユーザーから見えるのでIDやパスワード格納するには向かない | ○ リンクを使って他のユーザーと情報共有できる |
post | メッセージのボディ部 例:http://localhost:8080/login1 | ○ ユーザーから見えないのでIDやパスワードを格納できる | × リンクを使って他のユーザーと情報共有できない |
では、ここでもう少し踏み込んでgetメソッドとpostメソッドの違いについて見ていきましょう。
ブラウザには開発用のツール(ディベロッパーツール)があります。例えば、当社の新人エンジニア研修で使用しているChromeの場合はブラウザの画面を右クリックして「検証」を選びます。(ショートカットキーはF12)
英語で表示表示された場合は、以下の設定画面で日本語にすることもできますので講師から説明を受けてください。

下図にある「ネットワーク」のタブをクリックします。

F5キーを押してリクエストとレスポンスのやり取りをもう一度実行します。
次に、「名前」のところでコントローラーのURLパターン(例.Login1)を選択して、右の「ヘッダー」をクリックします。
下図の上がpostメソッド、下がgetメソッドの表示例です。


- ①postとgetでリクエストURLはどう違いますか?
あなたの答え: |
ただし、postリクエストにするとリクエストパラメータがアドレスバーに表示されないというだけです。上図の「Payload」というタブを開くとパラメータを見ることができてしまいます。httpsを使った暗号化などをしないとパケットを中継する機器からは丸見えです。くれぐれも誤解のないように。(https通信ですと「Payload」のタブ自体が無くなります)
ちなみにステータスコードが「200 OK」になっていますね。HTTPプロトコルにおいてステータスコードとは、Webサーバからのレスポンスの意味を表す3桁の数字です。
ここで当社の新人エンジニア研修で見かけるステータスコードを下表にまとめておきます。
ステータスコード | 意味 |
---|---|
200 | 正しく表示されている |
403 | アクセスを禁止されている |
404 | ページが見つからない |
405 | このURLではサポートされていないメソッドでアクセスした(例:コントローラーに@GetMapping しかないのにpostリクエストした) |
500 | サーバ内部エラー |
次に下図のように「Cookie」をクリックしてください。
名前に「JSESSIONID」、値に32桁の16進数が入っていますね。

Cookie とは、Webサイトの訪問者の情報を一時的にクライアントに保存するための仕組みです。Cookie の目的はユーザーの識別です。近年 Cookie を使った広告のトラッキングが問題になっていることをご存じの方もいるかと思います。
そして今回作りたいログイン処理もこの Cookie を使った仕組みなのです。
次に、 JSESSIONID とはJ2EEを使用したWebアプリケーションにおいてデフォルトで使用されるセッションIDを表す番号です。JSESSIONID の先頭のJはJavaのJなのですね。
セッション【session】とは、ブラウザとWebサーバの一連の複数のリクエストとレスポンスの活動期間のことです。(例えば、複数の質問に回答する時間をQ&Aセッションといいますね。英語のsessionには複数の送信と受信の一連の流れ、というような意味があるのです)このセッションごとにIDが付いているわけです。
6. セッションIDの仕組み
下図を使いセッションIDの仕組みを解説します。
- クライアント(ブラウザ)からサーバにリクエストが送られます
- サーバはセッションIDを生成します
- サーバはレスポンスにセッションIDを入れてクライアントに返します
- クライアントはセッションIDをCookieに保存します
- 以降、クライアントは全てのリクエストにセッションIDを入れて送ります
- サーバはクライアントから送られたセッションIDとサーバに保存されたセッションIDを照合することによりユーザーを認証します
- それ以降は上記5,6の手順でリクエストとレスポンスを繰り返します
- セッションIDのタイムアウト(デフォルトでは30分間)または破棄(大抵はログアウト)するまでセッションは有効です
パスワードとは異なりセッションIDは1回限りの意味のない16進数なので盗まれたとしても実害は少ないです。
(ただし、そのセッションIDを悪用する攻撃もありますので注意が必要です。もちろん現在皆さんがお使いのブラウザでは簡単には盗まれないようになっていますのでひとまずは安心して大丈夫ですが)

7. セッションを使った買い物かごの概要
買い物かご(ショッピングカート)は、ユーザーが商品の車を選択し、決済するまで保持するための仕組みです。今回は、以下のような機能を実装します。
- 車の一覧を表示する
- 車を買い物かごに追加する
- 買い物かごの中身を表示する
- 買い物かごをクリアする

7.1 買い物かごを管理するクラス
セッションで管理する買い物かご用のCart
クラスを作成します。
package com.example.demo.model;
import java.util.ArrayList;
import java.util.List;
public class Cart {
private List<String> items = new ArrayList<>();
public List<String> getItems() {
return items;
}
public void addItem(String car) {
items.add(car);
}
public int getSize() {
return items.size();
}
public void clear() {
items.clear();
}
}
※今回は説明を単純にするために車をStringクラスで表現していますが、後でDTOを学んだ後にはCarDtoクラスで1台の車を表現して、名前以外にも価格などを取得できるように書き換えてみて下さい。
7.2 コントローラーの作成
次に、CartController
を作成し、セッションを利用して買い物かごの機能を実装します。
package com.example.demo.controller;
import java.util.Arrays;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.example.demo.model.Cart;
import jakarta.servlet.http.HttpSession;
@Controller
public class CartController {
private final List<String> cars = Arrays.asList( "セダン","クーペ", "SUV");
private String findByName(String carName) {
for (String car : cars) {
if (car.equals(carName)) {
return car;
}
}
return null;
}
@GetMapping("/cars")
public String showCars(Model model) {
model.addAttribute("cars", cars);
return "cars";
}
@GetMapping("/cart")
public String showCart(HttpSession session, Model model) {
Cart cart = (Cart) session.getAttribute("cart");
if (cart == null) {
cart = new Cart();
session.setAttribute("cart", cart);
}
model.addAttribute("cart", cart.getItems());
return "cart";
}
@GetMapping("/cart/add")
public String addToCart(@RequestParam("name") String name, HttpSession session) {
Cart cart = (Cart) session.getAttribute("cart");
if (cart == null) {
cart = new Cart();
session.setAttribute("cart", cart);
}
String car = findByName(name);
if (car != null) {
cart.addItem(car);
}
return "redirect:/cart";
}
@GetMapping("/cart/clear")
public String clearCart(HttpSession session) {
Cart cart = (Cart) session.getAttribute("cart");
if (cart != null) {
cart.clear();
}
return "redirect:/cart";
}
}
カートの内容を表示する
GET /cart
にアクセスすると、カートの内容を表示します。
HttpSession
を利用して、カートのデータをセッションに保存 します。
もしセッションにカートがない場合は、新しい Cart
インスタンスを作成して保存します。
cart.getItems()
(カート内のアイテムリスト)をビューに渡します。
指定した ID の車をカートに追加する
GET /cart/add?id=〇
にアクセスすると、指定した ID の車をカートに追加 します。
findById(id)
で ID に対応する CarDto
を探す見つかった場合、cart.addItem(car)
でカートに追加追加後、redirect:/cart
でカートのページにリダイレクト
カートを空にする
GET /cart/clear
にアクセスすると、カートを空にします。
cart.clear()
を呼び出し、カートの中身をリセットします。
その後、カートページ (/cart
) にリダイレクトします。
7.3 車一覧ページ
<!DOCTYPE html>
<html>
<head><title>車一覧</title></head>
<body>
<h1>車一覧</h1>
<ul>
<li th:each="car : ${cars}">
<span th:text="${car}"></span>
<a th:href="@{/cart/add(name=${car})}">カートに追加</a>
</li>
</ul>
<a href="/cart">カートを見る</a>
</body>
</html>
7.4 買い物かごページ
<!DOCTYPE html>
<html>
<head><title>買い物かご</title></head>
<body>
<h1>買い物かご</h1>
<ul>
<li th:each="item : ${cart}">
<span th:text="${item}" ></span>
</li>
</ul>
<p><a href="/cars">車一覧へ</a></p>
<p><a href="/cart/clear">カートを空にする</a></p>
</body>
</html>
第5章の今回は、Spring BootでHttpSession
を利用してログインの仕組みと買い物かごを実装しました。セッションを使うことで、ユーザーごとのデータを管理できることが理解できたと思います。
第6章は「HTMLに動きを与える!Thymeleafテンプレート超入門」です。
