前回は「フォームからデータを送る」方法を学びました。リクエストとレスポンスの仕組みが理解できたのではないでしょうか?

今回は、今までの知識を応用してログイン処理を作ってみます。

これまでの知識で入力画面に入力した内容をもとにThymeleafの出力画面に情報を表示できるようになりました。しかし、複数ページを跨いで情報の保持はできていません。ステートレスといって1対のリクエストとレスポンスで終了してしまうのがhttpプロトコルの基本です。

そこで必要となるのがセッション属性です。セッション属性を使うことで複数ページを跨いで情報の保持ができます。そしてログイン処理は複数ページを跨いで情報を保持する必要のある典型的な処理です。この章ではそんなセッション属性の特徴をログイン処理を実装することにより体験的に学びます。

セッション属性を使うことで複数ページを跨いで情報の保持ができる

調べてみましょう

HTTPプロトコルがステートレスな理由を調べてみましょう。一般的には以下のような事がいわれています。

  1. Webは当初、静的なドキュメントの取得を目的としていたため、ステートフルな処理が不要だった。
  2. 電子メールなどの既存のプロトコル(SMTP、POPなど)を参考にしていたため、HTTPも「リクエストが独立している」という設計が取り入れられた。
  3. HTTPは、ログインや買い物かごのような機能を想定していなかったため、サーバーがユーザーの状態を管理しないステートレスな設計が適していた。

1. ログイン処理とは

まずは一般的なログイン処理についてまとめておきましょう。皆さんも是非、普段お使いのWebアプリケーションで実験してみてください。(あるいは講師の実演をご覧ください)

突然ですがログインに関する質問です。

  1. ①一般にWebシステムにログインするにはどのような情報が必要ですか?
あなたの答え:
  • ②ログイン前と後では何が違いますか?(ログインすると何が良いのですか?)
あなたの答え:
  • ③あるブラウザでログイン中に同じブラウザで他のタブを開いたら新しい方のタブでログインは継続されますか?
あなたの答え:
  • ④あるブラウザ(例えばクローム)でログイン中に新しいブラウザ(例えばエッジ)を起動したら新しいブラウザでログインは継続されますか?
あなたの答え:
  • ⑤どんなときにWebアプリからログアウトしますか?
あなたの答え:

上記の実験からも明らかなように一般的にはログインとは以下のような処理を指します。


①ユーザーはブラウザからIDとパスワードといった自分だけが知る情報を使ってログインする

②ログイン後はそのユーザーだけが見られるページに遷移できるようになる

③ログイン中は同じブラウザで他のタブを開いてもログインされたままである

④しかし、同じユーザーが別のブラウザで同じページにアクセスしても再度ログイン処理が必要である

⑤ログアウト処理をする、ブラウザを終了するまたは一定時間(大抵は30分間だが銀行のように5分程度と短い場合も)が経過するとログアウトする


上記結果④から考えるとログイン処理はブラウザごとに固有の情報を利用していると推測できます。

また、結果⑤から考えるとログアウトのタイミングは技術者が個々に設定していることが分かります。

本章では、ログイン処理を実現するにはセッションという仕組みを理解することが必要になるということ、さらに同一セッションで有効なセッション属性を使うことで複数リクエストを跨いだデータ保持が可能になるということを解説していきたいと思います。

例えば、オンラインショッピングで買い物かごの仕組みが実現できるのは、かごの中身をこのセッション属性に入れているから複数ページに渡ってデータを保持できるのです。次の商品を見に行くたびに買い物かごの中身がクリアされてしまっては使い物になりませんからね。

セッション属性とは開始してから破棄されるまで同一ブラウザの同一セッションで有効なスコープです。

セッションの破棄は、「ログアウト処理」、「ブラウザを終了する」または「セッションタイムアウト」により実行されます

実は、前章までの知識で実現できるのは上記「①ユーザーはブラウザからIDとパスワードといった自分だけが知る情報を使ってログインする」だけです。

ここからはセッションを使って「②~⑤の機能」をどのように実装するかを見ていきましょう。
前章までの知識では「① ブラウザからIDとパスワードを入力して送信する」ことまでしかできませんでしたが、セッションを利用することで「②ログイン後はユーザー専用のページへ行けるようになる」「③同じブラウザの別タブでもログイン状態が継続される」「④別ブラウザからアクセスしたら再度ログインが必要になる」「⑤ログアウト、またはタイムアウトなどでセッションが破棄される」機能を実装できます。

2. ログイン処理の実装

2.1 セッションを使う基本的な流れ

一般的に、HttpSession を使ったログインの流れは次のようになります。

  1. ログイン画面からIDとパスワードを送信
    • ユーザーはブラウザ上でフォームにIDとパスワードを入力し、サーバへ送信します。
  2. サーバ側で認証処理(ユーザーの正当性チェック)
    • コントローラや認証クラスなどで、送られてきたIDとパスワードが正しいかを確認します。実際のシステムではデータベースや外部サービスを参照することが多いでしょう。
  3. 認証が成功したら HttpSession にユーザー情報を設定
    • session.setAttribute("userId", ユーザーIDなど) のように、ログインしたユーザーを一意に識別できる情報をセッションへ格納します。
  4. セッションが有効な間はログイン状態が保持される
    • 次の画面遷移以降、コントローラで session.getAttribute("userId") を取得して、ユーザーがログイン中かどうかを判定します。
  5. ログアウトまたはセッション破棄で終了
    • ユーザーが明示的にログアウトするか、ブラウザを閉じる、一定時間操作がない(タイムアウト)などでセッションが破棄されると、ログイン状態は失われます。

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";
    }
}

  1. @RequestParam("userId") String userId でフォームから送られてきた userId を直接受け取ります。
  2. "imai".equals(userId) && "p".equals(password) でログイン判定(本来はデータベースなどで認証しますが、ここでは固定文字列で簡易化)。
  3. session.setAttribute("userId", userId) でログインに成功したユーザーをセッションに保存。
  4. session.invalidate() でセッション全体を破棄し、ログアウト状態に戻します。
  5. リダイレクトが通常の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>
  1. コントローラで model.addAttribute("userId", loginUser) した値を th:text="${session.userId}" で表示しています。
  2. 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>

ここまでのまとめ

  1. ホーム画面 → ビデオ画面 → ホーム画面ログアウト → ログイン画面 の流れ。
  2. セッション (session.getAttribute("userId")) を使ってログイン状態を管理。
  3. 未ログイン時に /video にアクセスすると /login にリダイレクトされる

調べてみましょう

今回は学習用に簡易的なログイン処理の流れを説明しました。しかし、本来ログイン処理はPRGパターンを考慮しないといけません。PRGパターンについて調べてみましょう。

3. HttpSessionの基本操作のまとめ

HttpSessionHttpServletRequest から取得し、セッションにデータを格納・取得・削除できます。

3.1 セッションの取得

HttpSession session = request.getSession();




getSession() は、既存のセッションがあれば取得し、なければ新しく作成します。

すでにセッションがある場合は、同じセッションが取得されます。

3.2 セッションへのデータ保存

session.setAttribute("userId", "yamada");




setAttribute(String name, Object value) でセッションにデータを保存できます。

Object 型なので、任意のオブジェクトを保存できます。

session.setAttribute()メソッド

Modelの場合はmodel.addAttribute()でしたが、Sessionの場合はsetAttribute()です。セッションに設定するという意味が込められています。

setAttribute()メソッド

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 セッションのライフサイクルと注意点

  1. 同じブラウザの別タブ
    • セッションID(クッキー)を共有しているため、同じセッションとなりログイン状態も共有されます。
  2. 別のブラウザでアクセス
    • セッションID(クッキー)が異なるため、改めてログインが必要になります。
  3. セッションタイムアウト
    • サーバ側に設定された時間(たとえば30分)リクエストがないと、セッションは破棄されます。
  4. ログアウト処理
    • session.invalidate() によってログイン情報が破棄され、次回リクエスト以降はログイン状態を維持できません。
  5. セッションに保持する情報量
    • セッションに大量のデータを保持するとサーバメモリを圧迫し、パフォーマンスが低下する可能性があるため注意します。通常は最小限のユーザー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の属性は渡らない
セッション属性は引き継げる
転送可能範囲同一アプリケーション内のみ外部サーバーも可能
転送後のURLURLは変化なし(ControllerのURLのまま)転送先のURLに変化する
転送とリダイレクトの違い

実験

外部URLへのリダイレクト処理は以下のようになります。

@PostMapping("/login1")
public String login(@RequestParam("id") String id,
                    @RequestParam("pass") String pass,
                    Model model) {

    if ("imai".equals(id) && "p".equals(pass)) {
        model.addAttribute("id", id);
        return "member-only"; 
    } else {
        // 外部URLへのリダイレクト
        return "redirect:https://saycon.co.jp/";
    }
}

5. getメソッドとpostメソッドの違いの復習

ログイン処理では、getメソッドではなく、postメソッドを使っていました。

HTMLでも学んだ下図にあるpostメソッドの特徴をまとめました。

パラメータの格納場所セキュリティ共有
getURL
例:http://localhost:8080/login1
id=imai&pass=p
×
ユーザーから見えるのでIDやパスワード格納するには向かない

リンクを使って他のユーザーと情報共有できる
postメッセージのボディ部
例:http://localhost:8080/login1

ユーザーから見えないのでIDやパスワードを格納できる
×
リンクを使って他のユーザーと情報共有できない
getメソッドとpostメソッドの違い

実験

もしも、postリクエストを使わずにgetリクエストを使ったらログイン処理としてどのような不都合があるでしょうか?

上記login.htmlとLoginController.javaを書き換えて試してみましょう。

確かめた結果:

例題

以下のactionはgetとpostのどちらを使うべきでしょうか?

  1. 商品の検索画面でキーワードを送信し、一時的な結果をHTMLに表示する場合
  2. ユーザーパスワードなどの機密情報を送信するなどURLにパラメータを表示させたくない場合

では、ここでもう少し踏み込んでgetメソッドとpostメソッドの違いについて見ていきましょう。

ブラウザには開発用のツール(ディベロッパーツール)があります。例えば、当社の新人エンジニア研修で使用しているChromeの場合はブラウザの画面を右クリックして「検証」を選びます。(ショートカットキーはF12)

英語で表示表示された場合は、以下の設定画面で日本語にすることもできますので講師から説明を受けてください。

Google Chromeのディベロッパーツールを日本語化する

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

ネットワークタブ

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サーバ内部エラー
ステータスコード

調べてみましょう

余裕があれば、ジョークHTTPステータスコードの418を調べてみましょう。


次に下図のように「Cookie」をクリックしてください。

名前に「JSESSIONID」、値に32桁の16進数が入っていますね。

JSESSIONID

Cookie とは、Webサイトの訪問者の情報を一時的にクライアントに保存するための仕組みです。Cookie の目的はユーザーの識別です。近年 Cookie を使った広告のトラッキングが問題になっていることをご存じの方もいるかと思います。

そして今回作りたいログイン処理もこの Cookie を使った仕組みなのです。


次に、 JSESSIONID とはJ2EEを使用したWebアプリケーションにおいてデフォルトで使用されるセッションIDを表す番号です。JSESSIONID の先頭のJはJavaのJなのですね。

セッション【session】とは、ブラウザとWebサーバの一連の複数のリクエストとレスポンスの活動期間のことです。(例えば、複数の質問に回答する時間をQ&Aセッションといいますね。英語のsessionには複数の送信と受信の一連の流れ、というような意味があるのです)このセッションごとにIDが付いているわけです。

6. セッションIDの仕組み

下図を使いセッションIDの仕組みを解説します。

  1. クライアント(ブラウザ)からサーバにリクエストが送られます
  2. サーバはセッションIDを生成します
  3. サーバはレスポンスにセッションIDを入れてクライアントに返します
  4. クライアントはセッションIDをCookieに保存します
  5. 以降、クライアントは全てのリクエストにセッションIDを入れて送ります
  6. サーバはクライアントから送られたセッションIDとサーバに保存されたセッションIDを照合することによりユーザーを認証します
  7. それ以降は上記5,6の手順でリクエストとレスポンスを繰り返します
  8. セッションIDのタイムアウト(デフォルトでは30分間)または破棄(大抵はログアウト)するまでセッションは有効です

パスワードとは異なりセッションIDは1回限りの意味のない16進数なので盗まれたとしても実害は少ないです。

(ただし、そのセッションIDを悪用する攻撃もありますので注意が必要です。もちろん現在皆さんがお使いのブラウザでは簡単には盗まれないようになっていますのでひとまずは安心して大丈夫ですが)

セッションIDの仕組み

セッションIDを暗証番号キーに例える

セッションIDをホテルの暗証番号キーに例えると、

  • 暗証番号キーを知らないと部屋には入れない
  • 暗証番号キーを知っていれば部屋に入って荷物を出し入れできる

調べてみましょう

Cookieの保存場所を見てみましょう。

当社の新人研修で使用しているGoogle Chromeの場合のCookieは、C:\Users\Your_User_Name\AppData\Local\Google\Chrome\User Data\Default\Network
にある単一のファイルに保存されています(Your_User_Nameをあなたの実際のユーザーアカウント名に置き換えてください)。

なお、このファイルはユーザーが読むことや編集することはできませんので試してみましょう。

調べたことのメモ:

7. セッションを使った買い物かごの概要

買い物かご(ショッピングカート)は、ユーザーが商品の車を選択し、決済するまで保持するための仕組みです。今回は、以下のような機能を実装します。

  1. 車の一覧を表示する
  2. 車を買い物かごに追加する
  3. 買い物かごの中身を表示する
  4. 買い物かごをクリアする
実装する買い物かごのイメージ

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台の車を表現して、名前以外にも価格などを取得できるように書き換えてみて下さい。

例題

上記カートクラスを以下の方法でテストしなさい。上記クラスにmain()メソッドを追記すると今までは選べなかった「実行 > Javaアプリケーション」のメニューが表示されるようになります。

  1. カートの作成
  2. カートにセダンを追加("セダン"という文字列を追加、以下同様)
  3. カートにクーペを追加
  4. カートに入った車の台数を表示する
  5. カートを空にする
  6. カートに入った車の台数を表示する

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>

@GetMapping("/cart/add")のように、URLのパスに階層を持たせる理由

ここでは2つの理由を紹介します。

1. リソースの整理と管理

URLの階層構造を持たせることで、システム内のリソースを整理しやすくなります。

例えば、以下のようなURLを考えてみましょう。

  • /cart → カートの情報を取得するエンドポイント
  • /cart/add → カートにアイテムを追加するエンドポイント
  • /cart/remove → カートからアイテムを削除するエンドポイント

もし/addや/removeのようにトップレベルで設置すると、どのリソースに対する操作かが分かりにくくなります。そのため、/cart/addのように階層を設けて「カートに対する処理」であることを明示するのが一般的です。

2. 可読性とメンテナンス性

階層を整理することで、開発者がコードを読みやすくなり、URL構造を一目で理解できるようになります。

例えば、@GetMapping("/cart/add")と@GetMapping("/add")がある場合、後者は「何を追加するのか?」が分かりにくいです。一方、前者は「カートに追加する処理」だとすぐ分かります。

ブラウザの表示がおかしいときは?

セッションを使ったWebアプリケーションを開発しているとキャッシュが邪魔をして想定外の動きになることがあります。そのため開発時はブラウザのゲストモードで実行することをオススメします。(設定方法は以下のとおりですが、詳しくは講師がお話しします)

ゲストモードで実行する場合の設定方法

穴埋め問題

空欄①〜⑧に適切なコードを記入してください。

問題1(セッションスコープを使ったユーザー情報の管理)

次のコードは、Spring Bootでセッションスコープを使ってユーザー情報を保持するControllerです。

@Controller
@RequestMapping("user")
public class UserController {

    @GetMapping("/login")
    public String login() {
        return "login";
    }

    @PostMapping("/login")
    public String doLogin(@RequestParam String username, ①(       ) session) {  
        // ログイン処理(セッションに保存)
        if (username.isEmpty()) {
            return "redirect:/login";
        }
        session.②(       )("username", username);
        return "redirect:/home";
    }
}
@Controller
public class UserController {

    @GetMapping("/user/login")
    public String login() {
        return "login";
    }

    @PostMapping("/user/login")
    public String doLogin(@RequestParam String username, ①(       ) session) {  
        // ログイン処理(セッションに保存)
        if (username.isEmpty()) {
            return "redirect:/user/login";
        }
        session②(       )("username", username);
        return "redirect:/home";
    }
}


問題2(セッション属性の取得)

次のコードは、セッションに保存された ユーザー名を取得する コントローラーです。
以下の空欄を埋めてください。

@GetMapping("/dashboard")
public String dashboard(③(       ) session, Model model) {  
    String username = (String) session.④(       )("username");  // セッションから取得
    if (username == null || username.isEmpty()) {
        return "redirect:/login";
    }
    model.addAttribute("username", username);
    return "dashboard";
}

問題3(セッションスコープの破棄)

次のコードは、セッション情報を破棄して ログアウト処理を行う コントローラーです。
以下の空欄を埋めてください。

@GetMapping("/logout")
public String logout(⑤(       ) session) {
    session.⑥(       )();  // セッションを完全に破棄
    return "redirect:/login";
}

問題4HttpSession を使ったセッション操作 - カートの追加)

次のコードは、ショッピングカートにアイテムを追加する コントローラーです。
以下の空欄を埋めてください。

@PostMapping("/addCart")
public String addCart(HttpSession session, @RequestParam String item) {
    List<String> cart = (List<String>) session.⑦(           )("cart");
    if(cart == null) {
        cart = new ArrayList<>();
    }
    cart.add(item);
    session.setAttribute("cart", cart);
    return "redirect:/cart";
}

問題5(セッションの値を取り出す際の注意点)

次のコードは、セッションに保存されたカート情報を取得する コントローラーです。
以下の空欄を埋めてください。

@GetMapping("/cart")
public String showCart(HttpSession session, Model model) {
    List<String> cart = (⑧(              )) session.getAttribute("cart");
    if(cart == null) {
        cart = new ArrayList<>();
    }
    model.addAttribute("cartItems", cart);
    return "cart";
}

<まとめ:隣の人に正しく説明できたらチェックを付けましょう>

□ HTTPは1回のリクエストとレスポンスで完結する通信(ステートレス)なので、ログイン状態など「状態の保持」にはセッション(HttpSession)が必要。

□ ①session.setAttribute("key", value) でデータを保存 ②session.getAttribute("key") でデータを取得

□ HttpSessionを利用してセッションスコープでデータを管理できる ①session.setAttribute("key", value) でデータを保存 ②session.getAttribute("key") でデータを取得

□ ログイン処理では、フォーム入力されたユーザー名をセッションに保存、未入力の場合はリダイレクトでログイン画面に戻す

□ セッションデータを取得するには、コントローラーのメソッドでHttpSessionを引数に指定し、session.getAttribute()メソッドを使う

□ セッション全体を破棄するにはsession.invalidate() を使う

□ セッションから取得する際の型変換に注意(例:List cart = (List) session.getAttribute("cart");)

□ thymeleafでセッションから値を取得するときにはsession.をつける
 (例:th:text="${session.userId}")

第5章の今回は、Spring BootでHttpSessionを利用してログインの仕組みと買い物かごを実装しました。セッションを使うことで、ユーザーごとのデータを管理できることが理解できたと思います。

第6章は「HTMLに動きを与える!Thymeleafテンプレート超入門」です。