Spring SecurityでDB認証&BCryptでハッシュ化

# 概要
* Spring Securityで必要最低限のログイン機能を実装する。（権限周りは触れません。）
* ログインフォームなどはSpring Securityで用意されているものを使う。
* 仕組みはあまり理解できていないので、また別でまとめます。
* この投稿はとりあえず、動くようになったというところまで！
<br>

# 開発環境
* OS：Windows10
* IDE：eclipse 2019-12
* Java：13
* Spring boot：2.2.5(Gradle)
* DB：Oracle 12c
<br>

# Spring Securityの導入
## 依存関係で下記の4つを選択
* Spring Security
* Spring Web
* Spring Data JPA
* Oracle Driver ←使用するDBのDriverを選択してください
![2020-03-15-14-06-34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364697/5974c5b7-f98b-7fa8-8c46-c49d7d895f0c.png)


<br>

# DB接続情報をプロパティファイルに記述
* それぞれのDBに合った内容にしてください。

```:application.properties
spring.datasource.url=jdbc:oracle:thin:@//localhost:1521/[データベース名]
spring.datasource.username=[DBユーザー名]
spring.datasource.password=[パスワード]
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
```
<br>

# ユーザー情報のテーブルを作成
```SQL
create table USER_TABLE (
    USER_ID VARCHAR2(30 char)
  , FAMILY_NAME VARCHAR2(10 char) not null
  , FIRST_NAME VARCHAR2(10 char) not null
  , PASSWORD VARCHAR2(255) not null
  , constraint USER_TABLE_PKC primary key (USER_ID)
) ;
```
<br>

## データ登録
下記のパスワードは「pass」をハッシュ化したものです。
[この記事](https://qiita.com/NagaokaKenichi/items/2742749a93df15b55d24)を参考にしました。

```SQL
INSERT INTO
    USER_TABLE
VALUES(
    '0001'
   ,'テスト'
   ,'太郎'
   ,'$2a$10$w0C4tFU.SLFATC1Y6Y4uy.vMYsLXhlfvgnFP4dLbRjEa6/Ag1csKS'
   );
```
<br>

# Entityクラスを作成
```Java:UserTable.java
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "USER_TABLE")
public class UserTable {
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name = "USER_ID")
	private String userId;

	@Column(name = "FAMILY_NAME")
	private String familyName;

	@Column(name = "FIRST_NAME")
	private String firstName;

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

// Getter,Setterは省略

```
<br>

# リポジトリを作成
```java:UserTableRepository.java
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.azkz.entity.UserTable;

@Repository
public interface UserTableRepository extends JpaRepository<UserTable, String> {

	public UserTable findByUserId(String userId);

}
```
<br>

# SecurityConfigを作成
```java:DemoSecurityConfig.java
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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class DemoSecurityConfig extends WebSecurityConfigurerAdapter {

	//アクセス可能なURLの制限とログイン成功時の遷移先の指定をするメソッド
	@Override
	protected void configure(HttpSecurity httpSecurity) throws Exception {
		httpSecurity.authorizeRequests()
				.mvcMatchers("/").permitAll() // 「/」は誰でもアクセスできる
				.anyRequest().authenticated() // それ以外はログインが必要
				.and()
				.formLogin()
				.defaultSuccessUrl("/success"); // ログイン成功時には「/success」にGetリクエストをする
	}

	//入力されたパスワードをBCrypt方式でハッシュ化するメソッド
	@Bean
	protected PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
}
```
<br>

# UserDetailsServiceを作成
* DBからユーザ情報を取得し、DemoUserDetailsをインスタンス化する。

```java
DemoUserDetailsService.java
import org.springframework.beans.factory.annotation.Autowired;
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 com.azkz.entity.UserTable;
import com.azkz.repository.UserTableRepository;

@Service
public class DemoUserDetailsService implements UserDetailsService {

	@Autowired
	UserTableRepository userTableRepository;

	@Override
	public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException {

		//入力されたUserIDを条件にDBからデータを取得する
		UserTable userTable = userTableRepository.findByUserId(userId);

		// 入力値（ユーザーID、パスワード）とインスタンス化したDemoUserDetailsクラスを
		// SpringSecurityの内部で比較することで、ログインチェックを行っている。
		return new DemoUserDetails(userTable);

	}

}
```
<br>

# UserDetailsを作成
* このクラスが入力情報（ユーザID、パスワード）と比較されることになる(？)
* ログイン後のセッション情報もこれ

```java:DemoUserDetails.java
import java.util.Collections;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;

import com.azkz.entity.UserTable;

public class DemoUserDetails extends User {

	@Autowired
	UserTable userTable;

	public DemoUserDetails(UserTable userTable) {
		// 「Collections.emptySet()」は本来は権限名のコレクションとなる。今回は空。
		super(userTable.getUserId(), userTable.getPassword(),Collections.emptySet());
		this.userTable = userTable;
	}

	// セッション情報から独自の項目を取得するためのGetter

	public String getFirstName() {
		return this.userTable.getFirstName();
	}

	public String getFamilyName() {
		return this.userTable.getFamilyName();
	}

}
```
<br>

# Controllerを作成
* 成功時にログインユーザの情報を表示する

```java:DemoController.java
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.azkz.security.DemoUserDetails;

@RestController
public class DemoController {

	@GetMapping("/success")
	public DemoUserDetails loginSuccess() {
		// ログイン中のユーザ情報を取得
		DemoUserDetails demoUserDetails =
				(DemoUserDetails) SecurityContextHolder
									.getContext().getAuthentication().getPrincipal();

		return demoUserDetails;
	}
}
```
<br>

# 画面確認
## http://localhost:8080/login にアクセス
![2020-03-15-19-24-53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364697/ef04c91b-45e3-1a4f-9c08-9b8c6fa77585.png)

<br>

## ログイン成功
![2020-03-15-19-26-25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364697/3b61db99-833f-cd32-ca81-148b3ba82e85.png)

<br>

## ログイン失敗
![2020-03-15-19-28-26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364697/04c46453-1e16-81b0-aa44-d44d421c9f13.png)

<br>

# 終わりに
* 表面的なことしか書いておりませんが、間違い・不足点等がございましたら、ぜひご教示いただきたく存じます。何卒！