SpringBootでデータベースによるログイン認証とパスワードハッシュ化（暗号化）

# この記事はこれの続きです

https://qiita.com/waokitsune/items/bcb5ce85a912bc506bac

# 概要

前回まではユーザー台帳はハードコーディングしていました。

そこから…

* ユーザー台帳はデータベースに持たせる。
* データベースで保持するパスワードはハッシュ化（暗号化）。

と変更します。

# データベース側

MySQLを使います。

『neko』というスキーマーに『neko_user』というテーブルがあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/bcfc940f-4b8e-56c0-3ac9-a6311a2f3ce6.png)

あらかじめユーザー情報はインサートされています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/d442cd35-abc4-2cf9-1a1e-a4ba369e183b.png)

# パスワード平文をハッシュ化した文字列が欲しい時

「テスト用アカウントを作りたいので、テーブルに直接インサートしたい」
なんて時は下記のような一時的な使い捨てクラスを作ると楽でした。

```java:
package com.example.demo;


import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;


public final class PasswordHashEncode {

	
	public static void encode(String plain_password) {
		 String hash_password_ = new BCryptPasswordEncoder().encode(plain_password);
		 System.out.println("ハッシュ化されたパスワード:" + hash_password_);
	}
	
	
}
```


# ユーザー情報まわりのデータベース接続を担当する部品たち用意

**『主キーであるユーザー名の文字列をもらって、データベースからユーザー情報を拾って返す』**
という機能を用意します。

## Entity

```java:NekoUser.java
package com.example.demo;


import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Data;


@Entity
@Data
@Table(name = "neko_user")
public class NekoUser {
	
	
	public NekoUser() {}
	
	
	@Id
    private String user_name;
	
	
	private String password;

	
}
```

## Repository

```java:NekoUserRepository.java
package com.example.demo;

import org.springframework.data.jpa.repository.JpaRepository;

public interface NekoUserRepository extends JpaRepository<NekoUser, String> {
}
```

## Service

```java:NekoUserService.vaja
package com.example.demo;


import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


@Service
@Transactional
public class NekoUserService {

	
	@Autowired
    NekoUserRepository neko_user_repository;
	
    
    public Optional<NekoUser> findById(String user_name) {
    	return neko_user_repository.findById(user_name);
    }
     
    
}
```

# WebScurityConfigを変更


```java:NekoWebScurityConfig.java
package com.example.demo;


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 NekoWebScurityConfig {
	
	
	//=======================================================================================================================
	//ここは変更無し。
	@Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		
    	http
    	
    	.authorizeHttpRequests((requests) -> requests
    			
                //アクセス制限除外URL。
                .requestMatchers("/",
                		"/login-input",
                		"/okok/*")
                .permitAll()
                .anyRequest()
                .authenticated()
    			
    			)
    	
    	.formLogin((login) -> login
    			
    			//ユーザーIDとパスワードを受け取るパラメーター名。
                .usernameParameter("username")
                .passwordParameter("password")
                
                //ログイン実行ページ。
                //仮にLoginTryControllerクラスを作ってもその内容は無視されます。
                .loginProcessingUrl("/login-try")
                
                //ユーザーIDとパスワード入力画面。
                .loginPage("/login-input")
                
                //ログイン失敗時遷移先。
                //ログイン失敗用のページを用意しません。
                //それを用意すると
                //『ユーザーIDとパスワード入力画面へのリンクをクリック』
                //という無駄な操作が発生するため。
                .failureUrl("/login-input?error")
                                
                //ログイン成功時遷移先。
                .defaultSuccessUrl("/login-success", true)
                
                .permitAll()

    			)
    	
    	.logout((logout) -> logout
    			
    			//ログアウトを実行するURL。
    			.logoutUrl("/logout")
    			
    			//ログアウト時の遷移先。
    	        .logoutSuccessUrl("/login-input")
    	        
    	      	//HTTPセッションを無効に。
    	        .invalidateHttpSession(true)
    	        
    	        //ログアウト時にクッキーを削除。
    	        .deleteCookies("JSESSIONID")
    	        
    	        .permitAll()
    	        
    			);
    	
    	
    	return http.build();
    	
    	
	}
	
	
	//=======================================================================================================================
	//今回はここが追加されました。
	@Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
	
	
}
```

# UserDetailsServiceを変更


```java:NekoUserDetailsService.java
package com.example.demo;


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;


@Service
public class NekoUserDetailsService implements UserDetailsService {
	
	
	@Autowired
	NekoUserService neko_user_service;
	
	
	@Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		
		
		//主キーであるユーザー名の文字列をキーにして、
		//データベースからユーザー情報をとってきます。
		//ユーザーが存在しない時は例外投げます。
		NekoUser neko_user_ = neko_user_service.findById(username)
                .orElseThrow(() -> new UsernameNotFoundException(username + "なんていないが"));
		
		
		//ハッシュ化された文字列をデコードしたりなどの、
		//『パスワードが合っているか』の処理はコーディングしなくてよいです。
		//ユーザー名とハッシュ化されたパスワードを持っているユーザー情報を返してあげて、
		//認証まわりはフレームワークに任せる、といったイメージです。
		
		//Entityはあくまでデータベースの『テーブルのカラム構造（値を保持しておく器的なもの）』です。
		//『SpringBootのログインユーザー情報』ではないです。
		//なので『SpringBootのログインユーザー情報に、値をセットしてビルドしたものを返す』
		//という手順をふみます。
		return User.builder()
                .username(neko_user_.getUser_name())
                .password(neko_user_.getPassword())
                .build();
        
		
    }
	

}
```

# 最終的にこう

赤枠が追加 or 変更になったものです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/452cedc4-5e05-7630-4cd5-b784cd09984c.png)

PasswordHashEncode.javaはパスワード平文からハッシュ化文字列を取得したい時用のものなので、無くてもよいです。

# この記事の続き

https://qiita.com/waokitsune/items/2065050e53b4644b7aa3

# 参考サイトさん

https://zenn.dev/sthrok/articles/1a427f7a3f7f9a

# バージョン

Microsoft Windows [Version 10.0.22631.3527]
JAVA 17.0.10
Spring Boot v3.1.11
MySQL 5.7.38-log