かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

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 の環境を構築する予定でしたが、先にこの件を調査・修正します。

参照したサイト・書籍

目次

  1. build してみる
  2. Remember Me 認証が使えなくなった原因を調査・修正する
  3. 再度 build してみる

手順

build してみる

clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると、テストが1件失敗して BUILD FAILED のメッセージが出力されます。

f:id:ksby:20181103092916p:plain

失敗したテストで何が起きているのか確認するために、Project Tool Window で src/test を選択した後、コンテキストメニューを表示して「Run with Coverage」-「All Tests」を選択してテストを実行してみると、失敗したテストとエラーメッセージが以下のように出力されて、

f:id:ksby:20181103093424p:plain

エラーが出力された箇所は以下のテストの赤線の部分でした。

f:id:ksby:20181103093714p:plain

どうも Remember Me 認証が動かなくなっているようです。

Tomcat を起動して動作を確認してみます。Tomcat 起動後、http://localhost:8080/ にアクセスして tanaka.taro@sample.com / taro を入力 →「次回から自動的にログインする 」をチェックしてから「ログイン」ボタンをクリックします。

f:id:ksby:20181103094857p:plain

http://localhost:8080/admin/library の URL の画面が表示されるので、ブラウザを一旦終了させます。

f:id:ksby:20181103095044p:plain

ブラウザを起動してアドレスバーに http://localhost:8080/admin/library を入力すると Remember Me 認証が行われて、ログイン不要で画面が表示されるはず。。。なのですがログイン画面が表示されました。やはり Remember Me 認証が使えなくなっています。

f:id:ksby:20181103095202p:plain

Remember Me 認証が使えなくなった原因を調査・修正する

DEBUG ログが出力されるように設定を変更して、テストが失敗した時に何が起きているか見てみると、org.springframework.security.provisioning.InMemoryUserDetailsManager.loadUserByUsername で UsernameNotFoundException が発生していました。

f:id:ksby:20181103100416p:plain

InMemory のユーザだけチェックして、ユーザがいなかった場合に DB のユーザをチェックしていないようです。

stacktrace に出力されているソースの場所を追ってみると、org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices#processAutoLoginCookie で getUserDetailsService().loadUserByUsername(...) を呼び出しており、

f:id:ksby:20181103134715p:plain

getUserDetailsService().loadUserByUsername(...) は実際には org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator#loadUserByUsername を呼び出していて、

f:id:ksby:20181103135021p:plain

このメソッドでは AuthenticationManagerBuilder のリストから最初に AuthenticationManagerBuilder#getDefaultUserDetailsService で取得できた UserDetailsService の loadUserByUsername を呼び出しています。

AuthenticationManagerBuilder の設定は ksbysample.webapp.lending.config.WebSecurityConfig の中で以下のように行っていますが、

f:id:ksby:20181103135446p:plain

org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder#inMemoryAuthentication を見ると apply メソッドを呼び出していて、

f:id:ksby:20181103135800p:plain

apply メソッドは以下のようになっています。DEBUG モードで確認すると inMemoryAuthentication メソッドが呼び出された時には defaultUserDetailsService には org.springframework.security.provisioning.InMemoryUserDetailsManager がセットされていました。

f:id:ksby:20181103135934p:plain

まとめてみると以下の状況でした。

  • 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 が出力されました。

f:id:ksby:20181103145914p:plain

履歴

2018/11/03
初版発行。