【SpringBoot】ログイン時にエラーメッセージを表示したい

### 0.記事内容
ログイン時に存在しないメールアドレスまたは不正なパスワードが入力された場合に、ログイン画面にエラーメッセージを表示する場合に必要な実装について記す。

---

### **1. カスタムエラーメッセージの設定**
Spring Securityの `AuthenticationFailureHandler` を実装して、失敗時の処理をカスタマイズします。

```java:CustomAuthenticationFailureHandler.java
package com.app.cook_diary.security;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) throws IOException, ServletException {
        // エラーメッセージをリクエストに追加
        request.setAttribute("error", "メールアドレスまたはパスワードが正しくありません。");

        // エラーメッセージをログイン画面に渡すクエリパラメータを追加してリダイレクト
        response.sendRedirect("/login?error=true");
    }
}
```

---

### **2. Spring Security設定に追加**

Spring Securityの設定で、カスタム `AuthenticationFailureHandler` を登録します。

```java:SecurityConfig.java
package com.app.cook_diary.config;

import com.app.cook_diary.security.CustomAuthenticationFailureHandler;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, CustomAuthenticationFailureHandler failureHandler) throws Exception {
        http.csrf(AbstractHttpConfigurer::disable) // 必要に応じて無効化
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/", "/register", "/login", "/css/**", "/js/**").permitAll()
                        .anyRequest().authenticated()
                ) // 他は認証が必要
                .formLogin(form -> form
                        .loginPage("/login") // ログイン画面のURL
                        .usernameParameter("username") // ユーザー名にメールアドレスを使用
                        .passwordParameter("password") // パスワードのパラメータ名
                        .failureHandler(failureHandler) // カスタムハンドラーを設定
                        .defaultSuccessUrl("/dishes", true) // 認証成功後のリダイレクト先
                        .permitAll() // ログインページは全員アクセス可能
                )
                .logout(logout -> logout
                        .logoutUrl("/logout") // ログアウトのURL
                        .logoutSuccessUrl("/") // ログアウト成功後のリダイレクト先
                        .permitAll() // ログアウトは全員アクセス可能
                );

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

```

---

### **3. ログイン用コントローラ**
クエリパラメータ(error=true)が付与されている場合にエラーメッセージを表示するよう実装
```java:LoginController.java
package com.app.cook_diary.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class LoginController {

    @GetMapping("/login")
    public String showLoginPage(@RequestParam(value = "error", required = false) String error, Model model) {
        if (error != null) {
            model.addAttribute("errorMessage", "メールアドレスまたはパスワードが正しくありません。");
        }
        return "login";
    }
}
```
---
### **4. ログイン画面でエラーメッセージを表示**

```html:login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="ja">
<head>
    <meta charset="UTF-8">
    <title>ログイン</title>
    <link rel="stylesheet" th:href="@{/css/login.css}">
</head>
<body>
<h1>ログイン</h1>
<!-- エラーメッセージの表示 -->
<div th:if="${errorMessage}" style="color: red;">
    <p th:text="${errorMessage}"></p>
</div>
<form th:action="@{/login}" method="post">
    <label for="email">Email:</label>
    <input type="email" id="email" name="username" required>
    <br>
    <label for="password">パスワード:</label>
    <input type="password" id="password" name="password" required>
    <br>
    <button type="submit">ログイン</button>
</form>
<a th:href="@{/register}">新規登録</a>
</body>
</html>
```

---

### **5. 動作確認**
1. 誤ったメールアドレスまたはパスワードを入力してログインを試みる。
2. `/login?error=true` にリダイレクトされ、エラーメッセージが表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1202056/eb027dfd-3884-5692-8379-a18ea84b206d.png)



---
以上