Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その13 )( Remember Me 認証が使えなくなっていたので調査・修正する )
概要
記事一覧はこちらです。
Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その12 )( Spring Boot Actuator を導入する ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- 前回の記事の後に build するのを忘れていたので build したところ、Remember Me 認証が使えなくなっていることに気づきました。
- Docker で Prometheus+Grafana の環境を構築する予定でしたが、先にこの件を調査・修正します。
参照したサイト・書籍
目次
手順
build してみる
clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると、テストが1件失敗して BUILD FAILED のメッセージが出力されます。
失敗したテストで何が起きているのか確認するために、Project Tool Window で src/test を選択した後、コンテキストメニューを表示して「Run with Coverage」-「All Tests」を選択してテストを実行してみると、失敗したテストとエラーメッセージが以下のように出力されて、
エラーが出力された箇所は以下のテストの赤線の部分でした。
どうも Remember Me 認証が動かなくなっているようです。
Tomcat を起動して動作を確認してみます。Tomcat 起動後、http://localhost:8080/ にアクセスして tanaka.taro@sample.com / taro を入力 →「次回から自動的にログインする 」をチェックしてから「ログイン」ボタンをクリックします。
http://localhost:8080/admin/library の URL の画面が表示されるので、ブラウザを一旦終了させます。
ブラウザを起動してアドレスバーに http://localhost:8080/admin/library を入力すると Remember Me 認証が行われて、ログイン不要で画面が表示されるはず。。。なのですがログイン画面が表示されました。やはり Remember Me 認証が使えなくなっています。
Remember Me 認証が使えなくなった原因を調査・修正する
DEBUG ログが出力されるように設定を変更して、テストが失敗した時に何が起きているか見てみると、org.springframework.security.provisioning.InMemoryUserDetailsManager.loadUserByUsername
で UsernameNotFoundException が発生していました。
InMemory のユーザだけチェックして、ユーザがいなかった場合に DB のユーザをチェックしていないようです。
stacktrace に出力されているソースの場所を追ってみると、org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices#processAutoLoginCookie で getUserDetailsService().loadUserByUsername(...)
を呼び出しており、
getUserDetailsService().loadUserByUsername(...)
は実際には org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator#loadUserByUsername を呼び出していて、
このメソッドでは AuthenticationManagerBuilder のリストから最初に AuthenticationManagerBuilder#getDefaultUserDetailsService で取得できた UserDetailsService の loadUserByUsername を呼び出しています。
AuthenticationManagerBuilder の設定は ksbysample.webapp.lending.config.WebSecurityConfig の中で以下のように行っていますが、
org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder#inMemoryAuthentication を見ると apply メソッドを呼び出していて、
apply メソッドは以下のようになっています。DEBUG モードで確認すると inMemoryAuthentication メソッドが呼び出された時には defaultUserDetailsService には org.springframework.security.provisioning.InMemoryUserDetailsManager がセットされていました。
まとめてみると以下の状況でした。
- ksbysample.webapp.lending.config.WebSecurityConfig#configAuthentication で以下の順に呼び出しており、
auth.authenticationProvider(daoAuhthenticationProvider()).userDetailsService(userDetailsService);
auth.inMemoryAuthentication()...
- 最初の
auth.authenticationProvider(daoAuhthenticationProvider()).userDetailsService(userDetailsService);
で AuthenticationManagerBuilder の defaultUserDetailsService に userDetailsService がセットされるが、 - 次の
auth.inMemoryAuthentication()...
で defaultUserDetailsService に org.springframework.security.provisioning.InMemoryUserDetailsManager がセットされる。 - Remember Me 認証では defaultUserDetailsService にセットされている UserDetailsService しか使用されない。
- よって InMemory にセットされたユーザしか使用されず、DB に保存されたユーザが使用されないため、Remember-me cookie が存在しても UsernameNotFoundException が発生する。
AuthenticationManagerBuilder#userDetailsService で userDetailsService をセットした後に auth.inMemoryAuthentication()
を呼び出したため、InMemory の UserDetailsServce(InMemoryUserDetailsManager)が Remember Me 認証で使用される UserDetailsService になっていたのが原因でした。 AuthenticationManagerBuilder#userDetailsService を呼び出す前に auth.inMemoryAuthentication()
を呼び出すように修正します。
src/main/java/ksbysample/webapp/lending/config/WebSecurityConfig.java を以下のように変更します。
@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.inMemoryAuthentication().~
をauth.authenticationProvider(daoAuhthenticationProvider()).userDetailsService(userDetailsService);
の前に移動します。
再度 build してみる
再度 clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると、今度は BUILD SUCCESSFUL が出力されました。
履歴
2018/11/03
初版発行。