はじめに
Spring Bootの認証処理において、かねてから疑問に思っていたパスワードの指定方法について調べてみました。
概要
Spring BootとSpring Securityで、ユーザ名とパスワードを指定した認証処理を実装する方法を紹介します。
前提とする環境は以下のとおりです。
Version | |
---|---|
Java | 1.7 |
Spring Boot | 1.5.9.RELEASE |
【問題】 UserDetailsService
ではパスワードを指定できない
「Spring Boot 認証処理」でググると、UserDetailsService
1のloadUserByUsername
を使ったサンプルがたくさん見つかります。
私も書いたことがあります。 → Spring BootでBasic認証を使用する
しかし、以下のとおりloadUserByUsername
の引数はusername
のみとなっています。
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
このため、ユーザ名とパスワードを指定した認証処理が必要な場合には対応できません。
例えば以下のようなケースでは、loadUserByUsername
で対応することはできません。
- パスワードのハッシュ化をDB側で実施2しており、SQLのパラメータ値にパスワードを含める必要がある
- 認証を外部ライブラリで実施しており、APIにユーザ名とパスワードのセットを渡す必要がある
【解決策】 AbstractUserDetailsAuthenticationProvider
を利用する
UserDetailsService
ではなく、AbstractUserDetailsAuthenticationProvider
3を利用しましょう。
具体的には、AuthenticationProvider
のBeanをAbstractUserDetailsAuthenticationProvider
を利用して作ることで、ユーザ名とパスワードを利用した認証処理を簡易に実装することができます。
サンプルコード
@Configuration
public class MyConfigure extends WebSecurityConfigurerAdapter {
// ※動作確認用に、全URLをBasic認証必須とする
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().realmName("My sample realm"); // Basic認証の設定
http.authorizeRequests().anyRequest().authenticated(); // 認証が必要となるリクエストの設定
}
// ----------------------------------------
// Bean定義
// ----------------------------------------
@Bean
public AuthenticationProvider getAuthenticationProvider() {
// 自前のAuthenticationProviderを使用する
return new MyUserDetailsAuthenticationProvider();
}
}
// AbstractUserDetailsAuthenticationProviderを継承することで、retrieveUserのみを実装すればOK
public class MyUserDetailsAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private static final String DUMMY_PASSWORD = "DUMMY_PASSWORD"; // ※認証では使用しないため、値は何でもよい(nullや空文字列はNG)
private static final List<GrantedAuthority> AUTH_USER = AuthorityUtils.createAuthorityList("USER"); // ※本サンプルでは、全員この権限とする
// <<< N/A >>>
@Override protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {}
//---
@Override
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
String userId = username;
String password = (String) authentication.getCredentials();
// ユーザIDとパスワードをチェック
boolean isValid = AuthApi.isValidUserIdAndPassword(userId, password); // ※外部ライブラリのAPIで認証する擬似コード
if (!isValid) { throw new UsernameNotFoundException(username); }
// UserDetailsの実装(User)を生成し戻り値とする
return new User(username, DUMMY_PASSWORD, AUTH_USER);
}
}
最後に
冒頭に書いたとおり、loadUserByUsername
を使ったサンプルは豊富にあるのですが、なぜかパスワードを認証処理に使用するサンプルはあまり無いようでしたので調べて書いてみました。
誤りや改善点などあれば、ご指摘頂ければと思います。