Spring Security でデータベースを参照して認証を行う(できるだけ少ない手数で)

# 概要
- https://qiita.com/Yoshihiro-Hirose/items/744c8bff3c3720013946 の続き
- Spring Security を適用し、認証情報にデータベースを使用する。
- パスワードも平文で保存**しない**ようにする

# 手順
1. ビルドスクリプトに Spring Security と JPA を追加
    ```groovy:build.gradle
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    ```
1. 以下のconfigクラスを新規作成
    ```java
    package com.example.demo.config;
    
    import org.springframework.context.annotation.Bean;
    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;
    
    @EnableWebSecurity
    public class SecurityConfig2 {
    
    	@Bean
    	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    		http.authorizeRequests()
    				.anyRequest().authenticated()
    				.and()
    				.formLogin()
    				.defaultSuccessUrl("/success");
    		return http.build();
    	}
    
    	@Bean
    	public PasswordEncoder passwordEncoder() {
    		return new BCryptPasswordEncoder();
    	}
    }
    ```
1. 認証情報を持つテーブルに対応したエンティティクラスを用意
    ```java
    @Entity
    @Table(name = "account")
    public class Account {
    	@Id
    	@GeneratedValue(strategy = GenerationType.AUTO)
    	@Column(name = "id")
    	private int id;
    
    	@Column(name = "user_name")
    	private String userName;
    
    	@Column(name = "password")
    	private String password;
    
        // setter, getter 省略
    }
    ```
1. 3. を取得する Repository クラスを定義
    ```java
    @Repository
    public interface AccountRepository extends JpaRepository<Account, Integer> {
    
    	Account findByUserName(String userName);
    }
    ```

1. 認証に使うユーザ情報を取得するサービスを定義
    ```java
    package com.example.demo.security;
    
    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.stereotype.Service;
    
    import java.util.Collections;
    
    @Service
    public class AccountUserDetailsService implements UserDetailsService {
    
    	private final AccountRepository accountRepository;
    
    	@Autowired
    	public AccountUserDetailsService(AccountRepository accountRepository) {
    		this.accountRepository = accountRepository;
    	}
    
    	@Override
    	public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
    		Account account = accountRepository.findByUserName(userName);
    		return new User(account.getUserName(), account.getPassword(), Collections.emptyList());
    	}
    }
    ```

1. テストクラスなどでハッシュ化したパスワードを取得
    ```java
    @SpringBootTest
    public class PasswordTest {
    
    	@Autowired
    	private PasswordEncoder encoder;
    
    	@Test
    	public void encode() {
    		System.out.println(encoder.encode("pass"));
    		System.out.println(encoder.encode("password"));
    	}
    }
    ```
    ```:実行結果例
    $2a$10$T7dckfpU4EC0nKxEKKYukeTa1ZXGBtKc5ZrEFfVNZ/CGoYmUDRE3e
    $2a$10$hCZ9AJeq20LpMk.wRHJf0eUdHABHatUp2Tnb8uqe8J3/ZuJ4njun.
    ```
1. ハッシュ化したパスワードを使ってデータベースに登録
- H2 を使った例
    ```sql:schema.sql
    CREATE TABLE IF NOT EXISTS account (
      id        INTEGER         NOT NULL AUTO_INCREMENT,
      user_name VARCHAR(128)    NOT NULL,
      password   VARCHAR(512)    NOT NULL,
      PRIMARY KEY (id)
    );
    ```
    ```sql:data.sql
    INSERT INTO account (user_name, password) VALUES('user1', '$2a$10$T7dckfpU4EC0nKxEKKYukeTa1ZXGBtKc5ZrEFfVNZ/CGoYmUDRE3e');
    INSERT INTO account (user_name, password) VALUES('user2', '$2a$10$hCZ9AJeq20LpMk.wRHJf0eUdHABHatUp2Tnb8uqe8J3/ZuJ4njun.');
    ```
1. アプリケーションを起動して、適当なページにアクセスする
1. ログインページが表示されるので、`user1` `pass` などで認証する