Spring Securityで再認証を実装してみる
Spring Securiyで再認証するサンプルアプリを作ってみたいと思います
- この実装が正しいかどうかいまいちわかりません。もっとこうした方がいい等あればコメント頂けると嬉しいです
- バグがあるかもしれません
はじめに
再認証とは
ここでは 以下の 1 の事として話を進めます
-
重要な処理の前、機密性の高い情報を表示する前にユーザー認証を行うこと
-
パスワード変更、アカウント削除、バックアップコード表示、など
-
セッションハイジャック対策、CSRF対策
-
-
ログインしたままでいる場合、定期的にユーザー認証すること
- 参考
-
徳丸 浩. 体系的に学ぶ 安全なWebアプリケーションの作り方 第2版
- 4.6.2 推測可能なセッションID
- 成りすましの影響
- 5.2.7アカウント管理のまとめ
- 4.6.2 推測可能なセッションID
-
OWASP Application Security Verification Standard 4.0
- 3.7.1 機密性の高いトランザクションやアカウントの変更を許可する前に〜 -
NIST Digital Identity Guidelines
-
SP 800-63B Authentication and Lifecycle Management
- 7.2 Reauthentication
-
SP 800-63B Authentication and Lifecycle Management
-
作成するサンプルアプリ

以下の仕様で作成します
- トップページはログイン画面
- ログインするとMy Page画面を表示する
- Sub PageをクリックするとSub Page画面を表示する
- Settingsをクリックすると再認証の画面を表示する
- 再認証にパスするとSettings画面を表示する
- 現在ログインしているユーザーのみ再認証をパス可能
- 再認証は3分間有効とする、3分経過するとSettings画面を表示する際ふたたび再認証が必要となる
- Logoutをクリックするとトップページに戻る
- ログイン状態/再認証状態がクリアされる
環境
- macOS でやります
- IDEは IntelliJ IDEA を使います
- ブラウザは Chrome です
サンプルアプリプロジェクト
プロジェクト(sample-app)はSpring Bootアプリです。
spring initializr で以下の設定で作ったプロジェクトです。
- Kotlin(Java 11)
- Spring Web
- Spring Security
- Thymeleaf
- H2 Database
- Spring Data JPA
プロジェクトの初期状態
基本的な認証処理が実装済みの ↓ の状態のソースに対して処理を追加していきます。
-
Loginをクリックしたらログインする- ログイン処理の詳細は Login を参照
-
Sub Pageをクリックしたら Sub Page 画面を表示する -
Settingsをクリックしたら Settings 画面を表示する -
Logoutをクリックしたらログアウトする

Login
-
loginクリックでPOST /loginをリクエストする -
CustomUserDetailsServiceで UserID(usernameとして入力された情報) の存在を確認する
-
DaoAuthenticationProviderで Password をVerifyする -
認証成功となったら
SavedRequestAwareAuthenticationSuccessHandlerから/mypageにリダイレクトする

実装
権限(ROLE)を設定する
まずは ROLE_USER , ROLE_ADMIN という2つの権限を追加します。
認証をパスしたユーザーに権限を付与して ROLE_ADMIN 権限が付与されたユーザーだけが/settings にアクセスできるようにします。
ROLE_USER / ROLE_ADMIN の定義
単純なenumを作成します。
認証時に ユーザーに ROLE_USER を付与する
loadUserByUsername()では認証後にユーザーに付与される権限を authorities という配列で渡します。
ここに ROLE_USER を SimpleGrantedAuthority クラスに包んで渡します。
/settings には ROLE_ADMIN が付与されたユーザーだけがアクセスできるようにする
AppWebSecurityConfigクラスのconfigure() で設定します。
↑のソースだと ROLE_USER を付与するコードしか書いていないので 誰も /setting にアクセスできません。
なので Settingsボタンをクリックすると Forbidden(403) が発生します。

再認証を追加する
Settingsボタンをクリックしたら(再)認証画面を表示し、この画面から認証をパスしたユーザーに ROLE_ADMIN を付与するようにします。
普通にログインしたユーザーは ROLE_USER の権限しか持っていないので、Settings画面に遷移するときに再認証して ROLE_ADMIN権限をゲットするという動作になります。
Forbidden(403) が発生したら /login にリダイレクトする
AccessDeniedHandler をオーバーライドすることで Forbidden のときの動作をカスタマイズすることができます。
本来行きたかった場所をセッションに保存して /login にリダイレクトさせます。
↑で作成した CustomAccessDeniedHandler を Config に登録します。
再認証したらROLE_ADMIN を付与する
既にログインしている状態で loadUserByUsername() が実行されるということは 再認証 である、という判断で、ROLE_ADMIN を付与します。
このとき、ログインしているユーザーと別のユーザーの場合、再認証は失敗するように考慮します。
再認証で認証失敗したときの考慮が必要です。ログイン済みステータスをクリアしないようにカスタマイズします。
↑で作成した CustomUsernamePasswordAuthenticationFilter を Config に登録します。
再認証の有効期限をつける
ここまでの実装で再認証する動きの実装はできました。
現状はADMIN権限を取得するとそのユーザーがログアウトするまでずっとADMIN権限を持ったままです。
ここからは、一定時間でADMIN権限が無くなるように実装してみます。
ROLE_ADMIN に3分の有効期限をつけて付与する
ユーザーに付与するROLEは GrantedAuthority を継承したクラスであれば何でもOKです。有効期限の情報がついた ExpireGrantedAuthority クラスを作成します。
loadUserByUsername で↑で作成した ExpireGrantedAuthority に包んで ROLE_ADMIN を作成します。
権限の有効期限をチェックする
/settings に遷移するときに ROLE_ADMIN の有効期限をチェックして有効期限が切れていたら無効にするようにします。
AccessDecisionVoter を継承した AcceptAdminTokenVoter を作成します。この vote メソッドは /settings に遷移するときにその認可をするために呼び出されます。AccessDecisionVoter.ACCESS_DENIE を返すと否定、AccessDecisionVoter.ACCESS_GRANTED を返すと肯定という意味です。
ここで有効期限をチェックします。
↑で作成した AcceptAdminTokenVoter を Config に登録します。
まとめ
作成したサンプルアプリの内容をまとめます
-
ログインしたら 一般ユーザー権限(ROLE_USER)を付与する
-
一般ユーザーは /settings にアクセスできない
-
/settings にアクセスしようとしたタイミングで再認証をする
-
再認証にパスしたら 管理者権限(ROLE_ADMIN)を付与する
-
管理者権限があれば /settings にアクセスできる
-
管理者権限は 3分で有効期限が切れる
ソースはこちら参照
おつかれさまでした
Spring Securityは学習コストが高い!難しい!
Discussion