【Spring Boot】UserDetailsService を使用せずに Spring Security で認証を行う。

## 本文の目的
Spring Securityを使った実装を行おう、と思った時にネットで検索すると、
``UserDetailsService``インターフェイスの``loadUserByUsername​(String username)``メソッドにて実装を行っている例を多く見かけますが、これはusernameのみでユーザ検索をする設計になっており、usernameとpassword両方で検索するようにはなっていません。
多くの場合は、username（やid,emailなど）とpasswordの両方で認証を行いたい、かと思いますので、UserDetailsService を使用しないで実装してみましたので、備忘としてここに残しておきます。

## 前提
前回、[**こちら**](https://qiita.com/MMK3/items/108f08620bf8c69376d2)でUserDetailsServiceを使用しての実装を行っており、今回はその差分のみの記載である。

## 前回との変更点
- 前回は、``CustomUserDetailsService``クラスで``UserDetailsService``をimplementsし、``loadUserByUsername​(String username)``メソッドにて実装を行ったが、今回は``AbstractUserDetailsAuthenticationProvider``クラスを継承したクラスを作成することで、usernameとpassword両方で検索を行う。

## 構成
構成は下記の通り。
[前回](https://qiita.com/MMK3/items/108f08620bf8c69376d2#%E6%A7%8B%E6%88%90)との差分も記載している。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2711483/2068c3e5-d054-705b-6bae-3e0abdb0251f.png)

## AbstractUserDetailsAuthenticationProvider クラスを継承したクラスを作成する
``UserDetailsService``を実装しない代わりに、``AbstractUserDetailsAuthenticationProvider`` クラスを継承したクラスを作成する。
``AbstractUserDetailsAuthenticationProvider`` クラスは、ユーザー名とパスワードを認証（比較）する``AuthenticationProvider`` インターフェイスを実装したものである。``AuthenticationProvider``を直接実装してもよいが、``AuthenticationProvider``のBeanを``AbstractUserDetailsAuthenticationProvider``を利用して作ることで、ユーザ名とパスワードを利用した認証処理を簡単に作成できるようなので、今回はそれで実装する。

~~~java:CustomAbstractUserDetailsAuthenticationProvider.java
package com.example.authentication;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import com.example.entity.User;
import com.example.repository.UserRepository;

public class CustomAbstractUserDetailsAuthenticationProvider 
extends AbstractUserDetailsAuthenticationProvider // AbstractUserDetailsAuthenticationProviderを継承する
{
	
	@Autowired
	private UserRepository userRepository;
	
    // @EnableWebSecurityをつけたconfigクラスにてBCryptPasswordEncoderをBean登録しているので、ここで注入する。
	@Autowired
	private BCryptPasswordEncoder bCryptPasswordEncoder;

    // UserDetails に何かしらの追加チェックを行いたい場合はここに実装。今回は要件にないので実装なし。
	@Override
	protected void additionalAuthenticationChecks(UserDetails userDetails,
			UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {}

	@Override
	protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		String password = (String) authentication.getCredentials(); // authenticationからpasswordを取得
		User user = userRepository.findByName(username); // usernameでDBの検索を行う。
		if(bCryptPasswordEncoder.matches(password, user1.getPassword())) { // 入力されたパスワードとDBにあったパスワードが一致するか判定
			return new CustomUserDetails(user); // 一致したらUserDetailsをnewしてreturn
		}else {
			throw new UsernameNotFoundException("user not found"); // 一致しなかったらUsernameNotFoundExceptionをスロー
		}
		
	}

}
~~~

### 今作成したCustomAbstractUserDetailsAuthenticationProviderをBean登録する

~~~java:MyConfigure.java
package com.example.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;

import com.example.authentication.CustomAbstractUserDetailsAuthenticationProvider;

@Configuration
public class MyConfigure {
	
    // 今作成したCustomAbstractUserDetailsAuthenticationProviderをBean登録する。
	@Bean
	public AuthenticationProvider getAuthenticationProvider() {
		return new CustomAbstractUserDetailsAuthenticationProvider();
	}
	
}
~~~

## 終わりに
loadUserByUsername​を使っていた時のもやもや解消のために実装しました。``UserDetailsService``を使わずに実装したいと思っている方の一助に少しでもなれば幸いです。
間違っている部分もあるかと思いますので、その際はご指摘頂ければと思います。


## 参考文献
[Spring SecurityでDB認証をする | Spring側](https://zenn.dev/misaka/books/9734700544990d/viewer/4f8a99)
[github.com/yukihane/hello-java/](https://github.com/yukihane/hello-java/tree/main/spring/springboot-auth-example-202006)
[Spring Bootで、ユーザ名とパスワードを指定した認証処理の実装方法](https://qiita.com/pale2f/items/3fb28e76f969d7c18f06)
[UserDetailsServiceのloadUserByUsernameの存在意義がよくわからないです](https://ja.stackoverflow.com/questions/67596/userdetailsservice%E3%81%AEloaduserbyusername%E3%81%AE%E5%AD%98%E5%9C%A8%E6%84%8F%E7%BE%A9%E3%81%8C%E3%82%88%E3%81%8F%E3%82%8F%E3%81%8B%E3%82%89%E3%81%AA%E3%81%84%E3%81%A7%E3%81%99)
[インターフェース AuthenticationProvider](https://spring.pleiades.io/spring-security/site/docs/current/api/org/springframework/security/authentication/AuthenticationProvider.html)
[クラス AbstractUserDetailsAuthenticationProvider](https://spring.pleiades.io/spring-security/site/docs/current/api/org/springframework/security/authentication/dao/AbstractUserDetailsAuthenticationProvider.html)
[Spring Securityで独自の認証項目を追加する](https://qiita.com/someone7140/items/a22f9e5e52f115cb5eeb)
[Spring Securityのカスタム認証-AuthenticationProvider vs UserDetailsS​​ervice](https://www.web-dev-qa-db-ja.com/ja/java/spring-security%E3%81%AE%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E8%AA%8D%E8%A8%BCauthenticationprovider-vs-userdetailss%E2%80%8B%E2%80%8Bervice/1054237962/)