【Spring Boot】SpringSecurityのログイン時の処理のカスタマイズ

## 概要
・Spring Securityを使用してログイン機能を実装していることを前提とします。
・今回、私がカスタマイズした処理の内容は【ログイン成功時にその日時を最終ログイン日時としてDBに保存する】という処理です。

※初記事なので分かりにくい場合や間違えてる場合等があるかもしれませんのでご了承ください...

## 実行環境
・Eclipse(2024)
・Spring Boot 3.3.0
・Spring Security 6

## 手順
### 1.CustomAuthenticationSuccessHandlerクラスの作成
　・AuthenticationSuccessHandlerインターフェースを実装したカスタムハンドラを作成します。
　・ログイン成功時にユーザの最終ログイン日時を更新するロジックを実装します。
 
### 2.ユーザサービスの作成
　・ユーザの最終ログイン日時を更新するためのメソッドを含むサービスクラスを作成します。

### 3.セキュリティ設定の更新
　・カスタムハンドラをSpring Securityの設定に登録します。

 ### 4. Userエンティティの更新


## 1.CustomAuthenticationSuccessHandlerクラスの作成
まず`AuthenticationSuccessHandler`を継承したカスタムハンドラクラスを作成します。
※このハンドラは、ログインが成功した時に呼び出されます。
```java:CustomAuthenticationSuccessHandler.java
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDateTime;

@Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private UserService userService;

    // コンストラクタでUserServiceをインジェクション
    public CustomAuthenticationSuccessHandler(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        // 認証されたユーザの名前を取得
        String username = authentication.getName();
        // 現在の日時を最終ログイン日時としてUserServiceに渡して更新
        userService.updateLastLogin(username, LocalDateTime.now());
        // ログイン成功後にリダイレクトするURL
        response.sendRedirect("/home");
    }
}
```
#### ポイント
・`CustomAuthenticationSuccessHandler`は`AuthenticationSuccessHandler`インターフェースを実装しています。
・`onAuthenticationSuccess`メソッドはログイン成功時に呼び出され、ユーザの名前を取得し、現在の日時を最終ログイン日時として更新するために`UserService`を呼び出します。

## 2.ユーザサービスの作成
ユーザの最終ログイン日時を更新するためのメソッドを持つサービスクラスを作成します。
```java:UserService.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public void updateLastLogin(String username, LocalDateTime lastLogin) {
        // ユーザ名でユーザを検索
        User user = userRepository.findByUsername(username);
        if (user != null) {
            // ユーザが存在する場合、最終ログイン日時を設定
            user.setLastLogin(lastLogin);
            // ユーザ情報を保存
            userRepository.save(user);
        }
    }
}
```
#### ポイント
・`UserService`クラスは、`UserRepository`を使ってユーザ情報を操作します(`UserRepository`の記述は省略)。
・`updateLastLogin`メソッドは、ユーザ名でユーザを検索し、最終ログイン日時を設定して保存します。

## 3.セキュリティ設定の更新
カスタムハンドラをSpring Securityの設定に登録します。
```java:SecurityConfig.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import com.example.web.service.AccountDetailsService;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Autowired
    private AuthenticationSuccessHandler customAuthenticationSuccessHandler;

	@Bean
	public SecurityFilterChain securityFilterilterChain(HttpSecurity http) throws Exception{
		http.formLogin(login -> login
				.loginPage("/login").permitAll()
				.usernameParameter("loginId")
				.passwordParameter("password")
				.defaultSuccessUrl("/", true)
				.failureUrl("/login?error")
				.successHandler(customAuthenticationSuccessHandler)) // 成功時のハンドラを指定
		.authorizeHttpRequests(authz -> authz
				.requestMatchers("/confirmation").permitAll()
				.requestMatchers("/css/**").permitAll()
				.requestMatchers("/js/**").permitAll()
				.requestMatchers("/images/**").permitAll()
				.anyRequest().authenticated())
		.logout(logout -> logout
				.logoutUrl("/logout"))
		return http.build();
	}

	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder(); // パスワードのエンコーダーとしてBCryptを使用
	}
}
```
#### ポイント
・`.successHandler`メソッドでカスタムハンドラを設定します。

## 4. Userエンティティの更新
ユーザエンティティに最終ログイン日時のフィールドを追加します。
```java:User.java
import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
@Data
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;
    private String password;
    private LocalDateTime lastLogin; // 最終ログイン日時のフィールドを追加
}
```
#### ポイント
・`User`エンティティに`lastLogin`フィールドを追加します。
※ご自身のDBに合わせて`Entity`は作成してください。