📌

【SpringBoot】DBでログイン認証

2024/02/12に公開

概要

データベースを使ってログイン認証を行う方法です。

プロパティ(データベース)設定

application.yml
spring:
  datasource:
    url: jdbc:h2:mem:testdb
    driverClassName: org.h2.Driver
    username: sa
    password:
    
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
    show-sql: true
    defer-datasource-initialization: true

今回はH2DBを使用。

初期データのためのdata.sql

data.sql
INSERT INTO Hoge_Hoge (username, email, password, confirm_password, firstname, lastname) VALUES ('user1', 'user1@example.com', '$2a$10$EfndcdB1U4ETjWWCxCC5o.PDmePiH7sZ1x0Yp9xuAI93naHHXwSkC', '2', 'First1', 'Last1');
INSERT INTO Hoge_Hoge (username, email, password, confirm_password, firstname, lastname) VALUES ('user2', 'user2@example.com', '$2a$08$Il6VTDYiD8pa4q06gKWHhOsSC8FKL.5FOFqhrlDPllp/9KCCmk0TW', '33', 'First2', 'Last2');

自動で挿入されるSQL。
初期データなのでパスワードにはハッシュ化済みの値を入れています。
テスト用なのでconfirm_passwordに見えるようにおいてます。

コントローラー

HomeController.java
package com.example.demo.controller;

import org.springframework.stereotype.Controller;

@Controller
public class HomeController {}

Spring Securityのログインプロセスは後述するWebSecurityConfig.javaのloginProcessingUrlへPOSTすることで処理が発火しますので、コントローラーへのマッピング記述は不要になります。

セキュリティコンフィグ

WebSecurity.java
package com.example.demo.securingweb;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;


@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((requests) -> requests
                // アクセス制限をかけない
                .requestMatchers("/"
                        , "/login?error"
                        , "/h2-console/**")
                .permitAll()
                .anyRequest().authenticated()
            )
            .formLogin((login) -> login
                .usernameParameter("username")
                .passwordParameter("password")
                // ログインを実行するページ
                .loginProcessingUrl("/login")
                // ログイン画面
                .loginPage("/login")
                // ログイン失敗時のURL
                .failureUrl("/login?error")
                // ログインに成功した場合の遷移先
                .defaultSuccessUrl("/loginSuccess", true)
                // アクセス権
                .permitAll()

            )
            .logout((logout) -> logout
                 // ログアウトした場合の遷移先
                .permitAll());
        // H2コンソール操作用
        http.csrf().disable();
        http.headers().frameOptions().disable();
        
        return http.build();
    }
    
    // パスワードのハッシュ化
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
}

要の部分です。
従来のSecurityConfigを使った書き方は非推奨になりましたので、SecurityFilterChainを使います。

  • .usernameParameter("username")
  • .passwordParameter("password")
    これらのパラメータはHTMLのname属性と紐づけます。
    E-mailで認証する場合はname属性をusernameとしusernameParameterもusernameとすることで紐付けができます。
    H2コンソール操作用の記述はデバッグ用にアクセスができるようにしています。

ユーザー固有のデータを読み込むコアインターフェース

AppUserDetailsService.java
package com.example.demo.service;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import com.example.demo.model.AppUser;
import com.example.demo.repository.AppUserRepository;

@Service
public class AppUserDetailsService implements UserDetailsService{

    private final AppUserRepository appUserRepository;
    
    @Autowired
    public AppUserDetailsService(AppUserRepository appUserRepository, PasswordEncoder passwordEncoder) {
        this.appUserRepository = appUserRepository;
    }
    
    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        // DBベースのユーザー検索
        AppUser appUser = appUserRepository.findByEmail(email)
                .orElseThrow(() -> new UsernameNotFoundException("User not found with email: " + email));
        
        return User.builder()
                .username(appUser.getEmail()) // ユーザー名としてメールアドレスを使用
                .password(appUser.getPassword()) // ハッシュ化されたパスワード
                .roles("USER")
                .build();
        }
 
}

こちらも大事な部分。
ユーザー名に基づきユーザーを見つけます。
今回はE-mailを受け取っているのでメールアドレスからユーザーを見つけます。

MVC設定

MvcConfig.java
package com.example.demo.securingweb;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("login");
        registry.addViewController("/login").setViewName("login");
        registry.addViewController("/loginSuccess").setViewName("loginSuccess");
    }

}

ユーザーエンティティ

AppUser.java
package com.example.demo.model;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;

@Entity
@Table(name = "HOGE_HOGE")
public class AppUser {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID")
    private Long id;

    @NotBlank
    @Column(name = "PASSWORD")
    private String password;

    @NotBlank
    @Column(name = "CONFIRM_PASSWORD")
    private String confirmPassword;
    
    @NotBlank
    @Column(name = "USERNAME")
    private String username;

    @Email
    @NotBlank
    @Column(name = "EMAIL")
    private String email;

    @NotBlank
    @Column(name = "FIRSTNAME")
    private String firstname;

    @NotBlank
    @Column(name = "LASTNAME")
    private String lastname;

    public Long getId() {
	return id;
    }

    public void setId(Long id) {
	this.id = id;
    }

    public String getPassword() {
	return password;
    }

    public void setPassword(String password) {
	this.password = password;
    }

    public String getConfirmPassword() {
	return confirmPassword;
    }

    public void setConfirmPassword(String confirmPassword) {
	this.confirmPassword = confirmPassword;
    }

    public String getUsername() {
	return username;
    }

    public void setUsername(String username) {
	this.username = username;
    }

    public String getEmail() {
	return email;
    }

    public void setEmail(String email) {
	this.email = email;
    }

    public String getFirstname() {
	return firstname;
    }

    public void setFirstname(String firstname) {
	this.firstname = firstname;
    }

    public String getLastname() {
	return lastname;
    }

    public void setLastname(String lastname) {
	this.lastname = lastname;
    }

}

データーベースを操作するクラス

AppUserRepository.java
package com.example.demo.repository;

import java.util.Optional;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import com.example.demo.model.AppUser;

@Repository
public interface AppUserRepository extends CrudRepository<AppUser, Long>{
    // メールアドレスによりユーザーを検索
    Optional<AppUser> findByEmail(String email);
}

ユーザーエンティティのインスタンスを受け取り、DBへ検索をかける処理を担います。

HTML

login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{common-header :: head_fragment('ログイン')}">
</head>
<body class="bt-light">
  <!-- 背景色 -->
  <div class="container mt-5">
    <!-- コンテナ中央寄せ -->
    <h2 class="text-center">LOGIN</h2>
    <div class="row justify-content-center">
      <div class="col-md-6">
        <div th:if="${param.error}" class="alert alert-danger w-75 mx-auto">Invalid username and password.</div>
        <div th:if="${param.logout}" class="alert alert-success w-75 mx-auto">You have been logged out.</div>
        <!-- ログインフォーム -->
        <form th:action="@{/login}" method="POST" class="card card-body w-75 mx-auto mb-3">
          <div class="mb-3">
            <label for="email" class="form-label">E-mail:</label>
            <input type="text" class="form-control" id="email" name="username" required autofocus />
          </div>
          <div class="mb-3">
            <label for="password" class="form-label">Password:</label>
            <input type="password" class="form-control" id="password" name="password" required />
          </div>
          <button type="submit" class="btn btn-primary w-100" value="btnSignIn">Sign In</button>
        </form>
      </div>
    </div>
  </div>
</body>
</html>
loginSuccess.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="head_fragment(title)">
  <title th:text="${title}"></title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
  <meta charset="utf-8" />
</head>
</html>
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.0</version>
    <relativePath /> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.example</groupId>
  <artifactId>SpringSummary</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>SpringSummary</name>
  <description>Demo project for Spring Boot</description>
  <properties>
    <java.version>17</java.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jdbc</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <!-- Spring Data JPA -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <!-- jquery -->
    <dependency>
      <groupId>org.webjars</groupId>
      <artifactId>jquery</artifactId>
      <version>3.5.1</version>
    </dependency>
    <!-- bootstrap -->
    <dependency>
      <groupId>org.webjars</groupId>
      <artifactId>bootstrap</artifactId>
      <version>4.5.3</version>
    </dependency>
    <!-- webjars-locator -->
    <dependency>
      <groupId>org.webjars</groupId>
      <artifactId>webjars-locator</artifactId>
      <version>0.40</version>
    </dependency>
    <!-- Spring security -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
      <groupId>org.dbflute</groupId>
      <artifactId>dbflute-runtime</artifactId>
      <version>1.2.0-patch20190620-RC1</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <scope>runtime</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity5 -->
    <dependency>
      <groupId>org.thymeleaf.extras</groupId>
      <artifactId>thymeleaf-extras-springsecurity5</artifactId>
      <version>3.1.2.RELEASE</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <excludes>
            <exclude>
              <groupId>org.projectlombok</groupId>
              <artifactId>lombok</artifactId>
            </exclude>
          </excludes>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

おまけとして少し機能を足したコード

DBと接続できているか、パスワードハッシュ化がうまくいっているか等確かめることができます。

ユーザー追加

★新規追加ファイル RegistrationController.java
package com.example.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.example.demo.model.AppUser;
import com.example.demo.service.AppUserDetailsService;

@Controller
@RequestMapping("/register")
public class RegistrationController {

    @Autowired
    private AppUserDetailsService userService;
    
    @GetMapping
    public String showRegistrationForm(Model model) {
      model.addAttribute("user", new AppUser());
      return "register";
    }
    
    @PostMapping
    public String registerUser(@ModelAttribute AppUser user, Model model) {
      try {
          userService.saveUser(user);
          model.addAttribute("registrationSuccess", true);
          model.addAttribute("registeredUser", user);
      } catch (Exception e) {
          model.addAttribute("registrationSuccess", false);
      }
        return "registerSuccess";
    }
    
}

全件表示

★新規追加ファイル showTable.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>全件表示</title>
</head>
<body>
    <h2>ユーザー一覧</h2>
    <div th:if="${users.isEmpty()}">
      登録データがありません。
    </div>
    <table th:if="${not users.isEmpty()}">
        <tr>
            <th>ID</th>
            <th>名前</th>
            <th>E-Mail</th>
            <!-- 他のカラム -->
        </tr>
        <tr th:each="user : ${users}">
            <td th:text="${user.id}">ID</td>
            <td th:text="${user.username}">名前</td>
            <td th:text="${user.email}">E-Mail</td>
            <td th:text="${user.password}">Password</td>
            <td th:text="${user.confirmPassword}">ConfirmPassword</td>
            <!-- 他のデータの表示 -->
        </tr>
    </table>
</body>
</html>

新規登録ページのコントローラー

★新規追加ファイル RegistrationController.java
package com.example.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.example.demo.model.AppUser;
import com.example.demo.service.AppUserDetailsService;

@Controller
@RequestMapping("/register")
public class RegistrationController {

    @Autowired
    private AppUserDetailsService userService;
    
    @GetMapping
    public String showRegistrationForm(Model model) {
      model.addAttribute("user", new AppUser());
      return "register";
    }
    
    @PostMapping
    public String registerUser(@ModelAttribute AppUser user, Model model) {
      try {
          userService.saveUser(user);
          model.addAttribute("registrationSuccess", true);
          model.addAttribute("registeredUser", user);
      } catch (Exception e) {
          model.addAttribute("registrationSuccess", false);
      }
        return "registerSuccess";
    }
    
}
HomeController.java
package com.example.demo.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import com.example.demo.model.AppUser;
import com.example.demo.repository.AppUserRepository;
import com.example.demo.service.AppUserDetailsService;

@Controller
public class HomeController {
  @Autowired
  private AppUserRepository userRepository;

  @Autowired
  private PasswordEncoder passwordEncoder;
  
  @Autowired
  private AppUserDetailsService AppUserDetailsService;
  
  @GetMapping("/showTable")
  public String showTable(Model model) {
      List<AppUser> users = AppUserDetailsService.getAllUsers();
      model.addAttribute("users", users);
      return "showTable";
  }

  @PostMapping("/insertDummyData")
  public String insertDummyData() {
      AppUser dummyUser = new AppUser();
      dummyUser.setUsername("dummyUser");
      dummyUser.setEmail("dummy@example.com");
      dummyUser.setFirstname("Dummy");
      dummyUser.setLastname("User");
      dummyUser.setConfirmPassword("2");
      
      dummyUser.setPassword(passwordEncoder.encode("2"));

      userRepository.save(dummyUser);
      
      return "redirect:/login";
  }
}
AppUserDetailsService.java
package com.example.demo.service;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import com.example.demo.model.AppUser;
import com.example.demo.repository.AppUserRepository;

@Service
public class AppUserDetailsService implements UserDetailsService{

  private final AppUserRepository appUserRepository;
  private final PasswordEncoder passwordEncoder;
  
  @Autowired
  public AppUserDetailsService(AppUserRepository appUserRepository, PasswordEncoder passwordEncoder) {
      this.appUserRepository = appUserRepository;
      this.passwordEncoder = passwordEncoder; // コンストラクタを通して注入
  }
  
  @Override
  public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
    // テスト用のハードコーディングされたユーザー情報
    if (email.equals("hoge")) {
        return User.builder()
                .username("hoge")
                // 注入されたエンコーダーでパスワードをハッシュ化
                .password(passwordEncoder.encode("password"))
                .roles("USER")
                .build();
    }
    // DBベースのユーザー検索
    AppUser appUser = appUserRepository.findByEmail(email)
            .orElseThrow(() -> new UsernameNotFoundException("User not found with email: " + email));
    
    return User.builder()
            .username(appUser.getEmail()) // ユーザー名としてメールアドレスを使用
            .password(appUser.getPassword()) // ハッシュ化されたパスワード
            .roles("USER")
            .build();
    }
 
    // ユーザーを保存
    public void saveUser(AppUser user) {
        appUserRepository.save(user);
    }
    
    // 全件取得
    public List<AppUser> getAllUsers() {
        Iterable<AppUser> users = appUserRepository.findAll();
        return StreamSupport.stream(users.spliterator(), false)
                         .collect(Collectors.toList());
    }
}
WebSecurityConfig.java
package com.example.demo.securingweb;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;


@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((requests) -> requests
                // アクセス制限をかけない
                .requestMatchers("/"
                        , "/login?error"
                        , "/h2-console/**"
                        , "/register"
                        , "/showTable"
                        , "/insertDummyData")
                .permitAll()
                .anyRequest().authenticated()
            )
            .formLogin((login) -> login
                .usernameParameter("username")
                .passwordParameter("password")
                // ログインを実行するページ
                .loginProcessingUrl("/login")
                // ログイン画面
                .loginPage("/login")
                // ログイン失敗時のURL
                .failureUrl("/login?error")
                // ログインに成功した場合の遷移先
                .defaultSuccessUrl("/loginSuccess", true)
                // アクセス権
                .permitAll()

            )
            .logout((logout) -> logout
                 // ログアウトした場合の遷移先
                .permitAll());
        // H2コンソール操作用
        http.csrf().disable();
        http.headers().frameOptions().disable();
        
        return http.build();
    }
    
    // パスワードのハッシュ化
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
}

HTML

★新規追加ファイル register.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>新規登録</title>
</head>
<body>
  <form th:action="@{/register}" th:object="${user}" method="post">
    <label for="firstname">性: </label>
    <input type="text" th:field="*{firstname}">
    <br>
    
    <label for="lastname">名:</label>
    <input type="text" th:field="*{lastname}">
    <br>
    
    <label for="username">ユーザー名:</label>
    <input type="text" th:field="*{username}">
    <br>
    
    <label for="password">パスワード:</label>
    <input type="password" th:field="*{password}">
    <br>
    
    <label for="confirmPassword">確認用パスワード: </label>
    <input type="password" th:field="*{confirmPassword}">
    <br>
    
    <label for="email">E-Mail:</label>
    <input type="email" th:field="*{email}">
    <br>

    <button type="submit" value="btnRegister">登録</button>
  </form>
</body>
</html>
★新規追加ファイル registerSuccess.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登録完了</title>
</head>
<body>
  <div th:if="${registrationSuccess}">
    <h2>登録が完了しました</h2>
  </div>
  <div th:if="${registrationSuccess == null or not registrationSuccess}">
    <h2>登録に失敗しました。</h2>
  </div>
  <table>
    <tr>
      <th>ID</th>
      <th>Password</th>
      <th>名前</th>
      <th>E-Mail</th>
    </tr>
    <tr>
      <td th:text="${registeredUser.id}">ID</td>
      <td th:text="${registeredUser.password}">Password</td>
      <td th:text="${registeredUser.username}">名前</td>
      <td th:text="${registeredUser.email}">E-Mail</td>
      </tr>
  </table>
  <button onclick="location.href='./login'">ログイン画面へ戻る</button>
</body>
</html>
login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{common-header :: head_fragment('ログイン')}">
</head>
<body class="bt-light">
  <div class="container mt-5">
    <h2 class="text-center">LOGIN</h2>
    <div class="row justify-content-center">
      <div class="col-md-6">
        <div th:if="${param.error}" class="alert alert-danger w-75 mx-auto">Invalid username and password.</div>
        <div th:if="${param.logout}" class="alert alert-success w-75 mx-auto">You have been logged out.</div>
        <!-- ログインフォーム -->
        <form th:action="@{/login}" method="POST" class="card card-body w-75 mx-auto mb-3">
          <div class="mb-3">
            <label for="email" class="form-label">E-mail:</label>
            <input type="text" class="form-control" id="email" name="username" required autofocus />
          </div>
          <div class="mb-3">
            <label for="password" class="form-label">Password:</label>
            <input type="password" class="form-control" id="password" name="password" required />
          </div>
          <button type="submit" class="btn btn-primary w-100" value="btnSignIn">Sign In</button>
        </form>
        <!-- 登録フォーム -->
        <form th:action="@{/register}" method="GET" class="card card-body w-75 mx-auto">
          <button type="submit" class="btn btn-success w-100" value="btnRegister">Register</button>
        </form>
      </div>
      <div>
        <form th:action="@{/insertDummyData}" method="POST">
            <button type="submit" name="action" value="btnInsertDummyData">ダミーデータ挿入</button>
        </form>
        <form th:action="@{/showTable}" method="get">
            <button type="submit" name="action" value="btnShowTable">全件表示</button>
        </form>
      </div>
    </div>
  </div>
</body>
</html>

未熟ですのでご指摘等あればよろしくお願いいたします。

開発環境
OS: Windows
開発ツール: Eclipse
言語: JDK17
フレームワーク: Spring Boot 3.2.0
DB: H2DB

Discussion