本文の目的
Spring Securityを使った実装を行おう、と思った時にネットで検索すると、
UserDetailsService
インターフェイスのloadUserByUsername(String username)
メソッドにて実装を行っている例を多く見かけますが、これはusernameのみでユーザ検索をする設計になっており、usernameとpassword両方で検索するようにはなっていません。
多くの場合は、username(やid,emailなど)とpasswordの両方で認証を行いたい、かと思いますので、UserDetailsService を使用しないで実装してみましたので、備忘としてここに残しておきます。
前提
前回、こちらでUserDetailsServiceを使用しての実装を行っており、今回はその差分のみの記載である。
前回との変更点
- 前回は、
CustomUserDetailsService
クラスでUserDetailsService
をimplementsし、loadUserByUsername(String username)
メソッドにて実装を行ったが、今回はAbstractUserDetailsAuthenticationProvider
クラスを継承したクラスを作成することで、usernameとpassword両方で検索を行う。
構成
構成は下記の通り。
前回との差分も記載している。
AbstractUserDetailsAuthenticationProvider クラスを継承したクラスを作成する
UserDetailsService
を実装しない代わりに、AbstractUserDetailsAuthenticationProvider
クラスを継承したクラスを作成する。
AbstractUserDetailsAuthenticationProvider
クラスは、ユーザー名とパスワードを認証(比較)するAuthenticationProvider
インターフェイスを実装したものである。AuthenticationProvider
を直接実装してもよいが、AuthenticationProvider
のBeanをAbstractUserDetailsAuthenticationProvider
を利用して作ることで、ユーザ名とパスワードを利用した認証処理を簡単に作成できるようなので、今回はそれで実装する。
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登録する
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側
github.com/yukihane/hello-java/
Spring Bootで、ユーザ名とパスワードを指定した認証処理の実装方法
UserDetailsServiceのloadUserByUsernameの存在意義がよくわからないです
インターフェース AuthenticationProvider
クラス AbstractUserDetailsAuthenticationProvider
Spring Securityで独自の認証項目を追加する
Spring Securityのカスタム認証-AuthenticationProvider vs UserDetailsService