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
権限をゲットするという動作になります。
/login
にリダイレクトする
Forbidden(403) が発生したら 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