Spring Securityを使ったWebアプリケーションの基本②　〜ログインとログアウト機能の実装〜


<h2>はじめに</h2>
<a href="https://qiita.com/homes31285/items/aa21f1d6c15b61ab811a">前回の記事</a>では、セキュリティの重要性やSpringSecurityのデフォルトのログイン機能についての説明をしました。今回はログイン機能とログアウト機能を実装していきたいと思います。

<details>
<summary>ファイル構成</summary>
<pre>
<code>
.
├── .gradle
├── .idea
├── build
├── gradle
└── src
     ├── main
     |      ├── java
     |      |      └── com
     |      |           └── example
     |      |                └── practice 
     |      |                  ├── config
     |      |                  |      └── SecurityConfig.java
     |      |                  ├── web 
     |      |                  |    ├── order
     |      |                  |    |    ├── OrderForm.java
     |      |                  |    |    └── OrderController.java
     |      |                  |    └── IndexController.java
     |      |                  └── domain
     |      |                        └── order 
     |      |                             ├── OrderEntity.java
     |      |                             ├── OrderService.java
     |      |                             └── OrderRepository.java
     |      └── resources
     |            ├── static
     |            ├── templates
     |            |     ├── order
     |            |     |     ├── delete_confirmation.html
     |            |     |     ├── detail.html 
     |            |     |     ├── form.html 
     |            |     |     └── list.html
     |            |     ├── login.html
     |            |     └── index.html
     |            ├── schema.sql
     |            ├── data.sql 
     |            └── application.properties
     └── test
.gitignore
build.gradle
gradlew.bat
HELP.md
settings.gradle
</code>
</pre>
</details>

<h2>ログイン画面の実装</h2>
まずはログイン画面のUIをコーディングしていきたいと思います。
このコードは、ユーザー名とパスワードを入力し、ログインボタンをクリックしてフォームを送信することで、ログイン処理を実行するための画面です。

```html:login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>ログインページ | 業務用アプリケーション</title>
  <!-- Bootstrap CSS link -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<!--ログインフォームを中央に配置するためのFlexコンテナ-->
<!--justify-content-centerとalign-items-centerはそれぞれ横方向と縦方向の中央配置-->
<div class="d-flex justify-content-center align-items-center">
  <!-- action属性はフォームが送信されるURLを指定し、th:action属性はThymeleafの式を使用してURLを設定する-->
  <form action="#" th:action="@{/login}" method="post">
    <div class="mt-3">
      <label for="usernameInput" class="form-label">ユーザー名</label>
<!-- name属性はフォームがサーバーに送信する際のキーとして使用される-->
      <input type="text" id="usernameInput" name="username" class="form-control">
    </div>
    <div class="mt-3">
      <label for="passwordInput" class="form-label">パスワード</label>
      <input type="password" id="passwordInput" name="password" class="form-control">
    </div>
    <div class="mt-3">
      <button type="submit" class="btn btn-primary">ログイン</button>
    </div>
  </form>
</div>
</body>
</html>
```

それでは、動作を確認していきましょう。
まずは、「<a>http://localhost:8080</a>」のURL入力し、<a>http://localhost:8080/login</a>に遷移できるかを試してみましょう。

ユーザー名をuserとし、パスワードはログの「Using generated security password:####」で表示されているものを入力してください。

![スクリーンショット 2023-08-26 13.39.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782718/cdf64c56-1a9d-000d-10b4-7a486ba0fff3.png)

ログインボタンを実行すると、以下のURLに遷移されることを確認してください。

![スクリーンショット 2023-08-26 13.40.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782718/5f821847-c9ad-562a-25f7-15cca2cdc265.png)

また、デベロッパーツールでも、確認していきましょう。

まずは、ユーザ名とパスワードを入力した状態から、デベロッパーツールを開きます。

![スクリーンショット 2023-08-26 18.23.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782718/7d8ab5e5-e404-4421-96ca-098246a12e85.png)

ネットワークのベイロードを開くと、usernameとpasswordが送信されていることがわかります。

![スクリーンショット 2023-08-26 18.30.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782718/3324e799-9e91-7372-1306-efa4ae7a5a74.png)

<h3>ログインエラーの実装</h3>
次にユーザー名とパスワードが失敗したときのコーディングをしていきたいと思います。
まずは、適当な値を入力し、ログイン状態が失敗するようにしてください。

そうすると、「<a>http://localhost:8080/login?error</a>」のurlに遷移します。

![スクリーンショット 2023-08-26 18.34.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782718/98dedb73-7552-f39e-07e3-68611aa2523f.png)

「?error」に遷移したときに、アラートが出るようにします。

```html:login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>ログインページ | 業務用アプリケーション</title>
  <!-- Bootstrap CSS link -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body class="bg-light">
<header class="mb-4" style="margin=5px">
  <h1>業務管理アプリケーション</h1>
</header>
<div class="d-flex justify-content-center align-items-center">
  <div class="text-left">
    <form action="#" th:action="@{/login}" method="post" style="width: 300px; margin: 0 auto;">
      <div class="form-group">
        <label for="usernameInput">ユーザー名</label>
        <input type="text" id="usernameInput" name="username" class="form-control">
      </div>
      <div class="form-group">
        <label for="passwordInput">パスワード</label>
        <input type="password" id="passwordInput" name="password" class="form-control">
      </div>
      <button type="submit" class="btn btn-primary">ログイン</button>
    </form>
    <!-- パラメータ error が存在する場合のみ表示 -->
    <div th:if="${param.error}" class="alert alert-danger mt-3" style="width: 300px; margin: 0 auto;">
      ユーザ名またはパスワードが違います
    </div>
  </div>
</div>
</body>
</html>
```

以下が「?error」に遷移したときに、アラートが出るようになりました。

![スクリーンショット 2023-08-28 19.43.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782718/a6802775-3e3d-bcc7-b16d-5a157bb65e81.png)

<h2>ログアウト機能の実装</h2>

まずは、index.htmlを修正したいと思います。
th:href="@{/logout}"で、/logoutへのURLが指定しています。
次に、th:attr="data-method='post'は、data-method 属性に 'post' の値をセットしています。これにより、リンクをクリックした際にPOSTメソッドでリクエストが送信されるようになります。

```html:index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>トップページ | 業務用アプリケーション</title>
    <!-- Bootstrap CSS link -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-4">
    <!-- ヘッダー部分 -->
    <header class="mb-4" style="padding: 10px; border-radius: 5px;">
        <div class="d-flex justify-content-between align-items-center">
            <h1 class="mb-0"><a href="../index.html" th:href="@{/}" style="color: black; text-decoration: none;">業務管理アプリケーション</a></h1>
            <a href="#" th:href="@{/logout}" th:attr="data-method='post'" class="btn btn-dark" style="font-size: 18px;">ログアウト</a>
        </div>
    </header>
    <!-- ヘッダー部分の最後 -->
    <ul class="list-group">
        <li class="list-group-item"><a href="./order/list.html" th:href="@{/orders}" class="btn btn-primary">注文リスト一覧</a></li>
    </ul>
</div>
</body>
</html>
```

次に、バックエンド側の処理をしていきたいと思います。
ログアウトのリンクをクリックした際に、バックエンドでセッションを無効にし、ログアウト処理を行いたいと思います。

以下の実装をすることで、logoutパスにGETリクエストが来た際に、セッションを無効にし、トップページにリダイレクトする処理が行われます。

```java:indexController
package com.example.practice.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@Controller
public class indexController {
    @GetMapping
    public String index() {
        return "index";
    }

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

    @GetMapping("/logout")
    public String logout(HttpServletRequest request, HttpServletResponse response) {
        // セッションを無効にする
        HttpSession session = request.getSession(false);
        if (session != null) {
            session.invalidate();
        }
        // ログアウト処理が完了したらトップページにリダイレクトする
        return "redirect:/";
    }
}
```

<h2>まとめ</h2>
この記事では、Spring Securityを使用してログイン機能とログアウト機能を実装する手順を紹介しました。以下にそれぞれの実装内容を振り返ってみましょう。

<h3>ログイン画面の実装</h3>
<ul>
  <li>Thymeleafを使用してログイン画面のUIをコーディングしました。</li>
  <li>ユーザー名とパスワードを入力し、ログインボタンをクリックすることでログイン処理を実行する仕組みを作成しました。</li>
</ul>
<h3>ログインエラーの実装</h3>
<ul>
  <li>ログインが失敗した場合、エラーメッセージを表示するためのコードを追加しました。</li>
  <li>URLに「?error」が含まれる場合にエラーアラートが表示されるようにしました。</li>
</ul>
<h3>ログアウト機能の実装</h3>
<ul>
  <li>トップページにログアウトのリンクを実装しました。</li>
  <li>Thymeleafを使用して、POSTメソッドを使用したログアウト処理を実現しました。</li>
  <li>バックエンドでログアウト処理を行うためのコードを実装しました。</li>
</ul>

このようにして、セキュリティを強化するための基本的なログイン機能とログアウト機能を実装することができました。今後の拡張やカスタマイズにもこの基盤を活用することで、より安全で使いやすいアプリケーションを構築することができます。