Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その27 )( ProviderManager#getProviders が DaoAuthenticationProvider を3つ返す原因を調査する )
概要
記事一覧はこちらです。
- 今回の手順で確認できるのは以下の内容です。
前回の記事において、org.springframework.security.authentication.ProviderManager#authenticate の中で
getProviders()
が DaoAuthenticationProvider を3つ返しているのを見つけたのですが、userDetailsService に ksbysample.webapp.lending.security.LendingUserDetailsService のインスタンスがセットされているものが2つありました(残りの1つは ImMemoryUserDetailsManager)。LendingUserDetailsService がセットされている DaoAuthenticationProvider が2つあると認証処理時に DB への検索処理が2回実行されるので、1つだけになるように変更します。
参照したサイト・書籍
目次
- WebSecurityConfig クラスから daoAuhthenticationProvider, configAuthentication メソッドをコメントアウトしてみる
- WebSecurityConfig クラスの configAuthentication メソッドで
.userDetailsService(userDetailsService)
をコメントアウトしてみる - WebSecurityConfig クラスから daoAuhthenticationProvider メソッドだけコメントアウトしてみる
- まとめてみると。。。
手順
WebSecurityConfig クラスから daoAuhthenticationProvider, configAuthentication メソッドをコメントアウトしてみる
src/main/java/ksbysample/webapp/lending/config/WebSecurityConfig.java から daoAuhthenticationProvider, configAuthentication メソッドをコメントアウトするとどうなるか確認してみます。
@Configuration public class WebSecurityConfig { .......... // /** // * @return ??? // */ // @Bean // public AuthenticationProvider daoAuhthenticationProvider() { // DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); // daoAuthenticationProvider.setUserDetailsService(userDetailsService); // daoAuthenticationProvider.setHideUserNotFoundExceptions(false); // daoAuthenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder()); // return daoAuthenticationProvider; // } // // /** // * @param auth ??? // * @throws Exception // */ // @SuppressWarnings("PMD.SignatureDeclareThrowsException") // @Autowired // public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception { // // AuthenticationManagerBuilder#userDetailsService の後に auth.inMemoryAuthentication() を呼び出すと // // AuthenticationManagerBuilder の defaultUserDetailsService に // // org.springframework.security.provisioning.InMemoryUserDetailsManager がセットされて // // Remember Me 認証で InMemoryUserDetailsManager が使用されて DB のユーザが参照されなくなるので、 // // Remember Me 認証で使用する UserDetailsService を一番最後に呼び出す // // ※今回の場合には auth.userDetailsService(userDetailsService) が一番最後に呼び出されるようにする // auth.inMemoryAuthentication() // .withUser("actuator") // .password("{noop}xxxxxxxx") // .roles("ENDPOINT_ADMIN"); // auth.authenticationProvider(daoAuhthenticationProvider()) // .userDetailsService(userDetailsService); // } }
「ログインを5回失敗すればアカウントはロックされる」のテストを debug 実行すると、org.springframework.security.authentication.ProviderManager#authenticate で getProviders()
から userDetailsService に ksbysample.webapp.lending.security.LendingUserDetailsService のインスタンスがセットされている DaoAuthenticationProvider が1つ返ってきていました。特に何もしなくても DaoAuthenticationProvider がセットされるようです。
テストも成功します。
ただし全てのテストを実行してみると「次回から自動的にログインするをチェックすれば次はログインしていなくてもログイン後の画面にアクセスできる」のテスト(Remember Me 認証のテスト)だけ失敗します。
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator.loadUserByUsername の以下の場所でエラーになっており、delegate する先の UserDetailsService が見つからないことが原因のようです。
WebSecurityConfig クラスの configAuthentication メソッドで .userDetailsService(userDetailsService)
をコメントアウトしてみる
今度は src/main/java/ksbysample/webapp/lending/config/WebSecurityConfig.java の configAuthentication メソッドで .userDetailsService(userDetailsService)
をコメントアウトしてみます。また passwordEncoder には BCryptPasswordEncoder のインスタンスをセットせず、デフォルトの DelegatingPasswordEncoder のインスタンスを使用します。
@Configuration public class WebSecurityConfig { .......... /** * @return ??? */ @Bean public AuthenticationProvider daoAuhthenticationProvider() { DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); daoAuthenticationProvider.setUserDetailsService(userDetailsService); daoAuthenticationProvider.setHideUserNotFoundExceptions(false); // daoAuthenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder()); return daoAuthenticationProvider; } /** * @param auth ??? * @throws Exception */ @SuppressWarnings("PMD.SignatureDeclareThrowsException") @Autowired public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception { // AuthenticationManagerBuilder#userDetailsService の後に auth.inMemoryAuthentication() を呼び出すと // AuthenticationManagerBuilder の defaultUserDetailsService に // org.springframework.security.provisioning.InMemoryUserDetailsManager がセットされて // Remember Me 認証で InMemoryUserDetailsManager が使用されて DB のユーザが参照されなくなるので、 // Remember Me 認証で使用する UserDetailsService を一番最後に呼び出す // ※今回の場合には auth.userDetailsService(userDetailsService) が一番最後に呼び出されるようにする auth.inMemoryAuthentication() .withUser("actuator") .password("{noop}xxxxxxxx") .roles("ENDPOINT_ADMIN"); auth.authenticationProvider(daoAuhthenticationProvider()); // .userDetailsService(userDetailsService); } }
「ログインを5回失敗すればアカウントはロックされる」のテストを debug 実行すると、org.springframework.security.authentication.ProviderManager#authenticate で getProviders()
から userDetailsService に ksbysample.webapp.lending.security.LendingUserDetailsService のインスタンスがセットされている DaoAuthenticationProvider と ImMemoryUserDetailsService のインスタンスがセットされている DaoAuthenticationProvider の計2つが返ってきていました。
テストは成功します。
全てのテストを実行してみると先程と同様に「次回から自動的にログインするをチェックすれば次はログインしていなくてもログイン後の画面にアクセスできる」のテスト(Remember Me 認証のテスト)だけ失敗します。ただし失敗の原因が異なり、今度は HTTPステータスコードが 200 ではなく 302 が返ってくるというものでした。
これは Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その13 )( Remember Me 認証が使えなくなっていたので調査・修正する ) で調査済ですが、Remember Me 認証用の UserDetailsService として org.springframework.security.provisioning.InMemoryUserDetailsManager がセットされた後に別の UserDetailsService をセットしていないので、InMemoryUserDetailsManager が認証処理に使用されて認証エラーになり、ログイン画面へリダイレクトさせられているのでしょう。
WebSecurityConfig クラスから daoAuhthenticationProvider メソッドだけコメントアウトしてみる
今度は src/main/java/ksbysample/webapp/lending/config/WebSecurityConfig.java から daoAuhthenticationProvider メソッドだけコメントアウトしてみます。
// /** // * @return ??? // */ // @Bean // public AuthenticationProvider daoAuhthenticationProvider() { // DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); // daoAuthenticationProvider.setUserDetailsService(userDetailsService); // daoAuthenticationProvider.setHideUserNotFoundExceptions(false); // daoAuthenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder()); // return daoAuthenticationProvider; // } /** * @param auth ??? * @throws Exception */ @SuppressWarnings("PMD.SignatureDeclareThrowsException") @Autowired public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception { // AuthenticationManagerBuilder#userDetailsService の後に auth.inMemoryAuthentication() を呼び出すと // AuthenticationManagerBuilder の defaultUserDetailsService に // org.springframework.security.provisioning.InMemoryUserDetailsManager がセットされて // Remember Me 認証で InMemoryUserDetailsManager が使用されて DB のユーザが参照されなくなるので、 // Remember Me 認証で使用する UserDetailsService を一番最後に呼び出す // ※今回の場合には auth.userDetailsService(userDetailsService) が一番最後に呼び出されるようにする auth.inMemoryAuthentication() .withUser("actuator") .password("{noop}xxxxxxxx") .roles("ENDPOINT_ADMIN"); // auth.authenticationProvider(daoAuhthenticationProvider()) // .userDetailsService(userDetailsService); auth.userDetailsService(userDetailsService); }
「ログインを5回失敗すればアカウントはロックされる」のテストを debug 実行すると、org.springframework.security.authentication.ProviderManager#authenticate で getProviders() から userDetailsService に ksbysample.webapp.lending.security.LendingUserDetailsService のインスタンスがセットされている DaoAuthenticationProvider と ImMemoryUserDetailsService のインスタンスがセットされている DaoAuthenticationProvider の計2つが返ってきていました。
テストは成功します。
全てのテストを実行してみると、全て成功しました。
まとめてみると。。。
- Remember Me 認証を使用しないのであれば、WebSecurityConfig クラスに daoAuhthenticationProvider, configAuthentication メソッドを定義する必要はない。
- Remember Me 認証を使用するのであれば、WebSecurityConfig クラスに configAuthentication メソッドを定義して、その中で AuthenticationManagerBuilder#userDetailsService を呼び出して認証処理に使用する UserDetailsService クラスのインスタンスをセットする。
- WebSecurityConfig クラスに configAuthentication メソッドを定義する場合、同じ UserDetailsService で AuthenticationManagerBuilder#authenticationProvider と AuthenticationManagerBuilder#userDetailsService を呼び出さないこと。認証処理時にセットした UserDetailsService で2回認証処理が行われることになる。
Remember Me 認証に対応しようとすると Spring Security は何気に難易度が上がる感じがします。
src/main/java/ksbysample/webapp/lending/config/WebSecurityConfig.java は daoAuhthenticationProvider メソッドだけコメントアウトする案を採用し、コメントアウトではなく削除で反映します。
履歴
2019/01/05
初版発行。