LoginSignup
44
39

More than 3 years have passed since last update.

Spring Security の認証処理アーキテクチャを学ぶ

Last updated at Posted at 2019-11-13

Spring Security は割となんとなくで動作してくれるイメージだけど、カスタマイズしようとするとハマったりするので、アーキテクチャを知れば何とか分かるようになるのではないかという発想から調べてまとめてみた。

以下のようなフォーム認証を行うシナリオに沿って、どのようなクラスがどのような役割を担っているのかを調べる。

  1. 未認証の状態で認証が必要なページへアクセスする
  2. ログイン画面にリダイレクトされる
  3. 情報を入力しログイン処理を実行する
  4. ログインが完了し 1でアクセスしていた画面にリダイレクトされる

なお、バージョンは 5.2.1.RELEASE で確認している。

ログイン画面へリダイレクトされるまで

認証が必要なページに対してまだ認証されていないユーザがアクセスした場合、ログイン画面へ飛ばされる仕組みについて。
ざっくりとしたフローは以下の通り。なお、今回はアクセスした際のチェック処理は説明しないが AuthenticationException がスローされるところからスタートする。

ログイン遷移.png

ExceptionTranslationFilter

発生した例外の原因例外を検査し、AuthenticationException が含まれている場合に、リクエストの内容をRequestCache に保存しつつ、AuthenticationEntryPoint を利用して認証処理を開始させる。
RequestCache は通常は HttpSession を利用してリクエスト内容を保存する HttpSessionRequestCache を利用する。
RequestCache が何に利用されるのかは後ほど。

AuthenticationEntryPoint

認証処理を開始する。
例えば、LoginUrlAuthenticationEntryPoint は設定されたログインページの URL に対してリダイレクトを行う。
BasicAuthenticationEntryPoint は Basic 認証を開始するために、ステータスコードを 401 に、header に WWW-Authenticate を付与したレスポンスを返す。

ログイン画面遷移まとめ

ExceptionTranslationFilter によってハンドリングされるが、あまりカスタムすることもないので意識しなくてよさそう。
どのように認証を開始するかは AuthenticationEntryPoint を適切に選択、設定する必要がある。
例えばログイン画面に遷移させるのであれば、LoginUrlAuthenticationEntryPoint を利用してログイン画面の URL を設定する必要があるし、SSO のように事前に認証される前提で、認証処理を開始する必要がないならば Http403ForbiddenEntryPoint を利用するなど。

DelegatingAuthenticationEntryPoint を利用すればリクエストに対して利用する実装クラスを定義することもできるっぽい。

認証処理

まずはユーザが入力した情報を受け取り、認証 OK となるまでの流れ。ざっくりと以下のようになっている。

認証.png

AuthenticationManager

認証をつかさどるのは AuthenticationManager
Authentication オブジェクトを受け取り、認証 OK の場合は Authentication オブジェクトを返す。
引数の Authentication は認証に必要な principal と credentials のみが入っていて、戻り値の Authentication には authorities や details などの情報も入っている。

ProviderManager

AuthenticationManager の実装クラスとして ProviderManager がある。デフォルトではこのクラスを利用する。
このクラスは複数の AuthenticationProvider に対して認証処理を委譲し、1 つでも認証 OK となれば認証成功とする。
こうすることによって、例えばデータベースに格納された情報を利用した Form 認証と、LDAP による認証など、複数の認証処理を組み合わせることができる。
また、戻り値となる Authentication から credentials を削除し、機密情報が永続化されることを防ぐことができる。

AuthenticationProvider

認証を行う。パスワードの検証が必要なのであればこのクラスで行う。

また、認証可能な Authentication の実装クラスであるかどうかを判定するメソッドがある。
例えば、通常の Form 認証であれば、ユーザ名とパスワードがあれば認証処理を行うことができるが、すべての認証処理がそれで充分だとは限らない。なので、認証処理を行うことができるかどうかを supports(Class<?> authentication) メソッドで判定する。

一般的な認証方法は実装クラスが提供されているので、基本的にそれらを利用すればいい。
ただし、ユーザの情報を取得してくる部分については、アプリケーションが利用しているデータベースなどの定義に依存するので、そこの部分のみ実装するというパターンが多い。

UserDetailsService

前述のユーザ情報の取得に関する部分の処理を行うためのインターフェース。
このインターフェースの実装クラスを作成してAuthenticationProvider に設定する、といった使い方をすることが多い。

取得する情報は UserDetails を実装したクラスでないといけない。

なお、JdbcDaoImpl という実装クラスも提供されているが、取得できる情報が限られるため、自分で実装することになると思う。

認証処理呼び出す

基本的には Filter が呼び出す。例えば Form 認証を行う場合は UsernamePasswordAuthenticationFilter が呼び出す。
この Filter は、ユーザからの入力情報などから Authenticaion の実装クラスである UsernamePasswordAuthenticationToken オブジェクトを作成し、AuthenticationManager の認証処理を呼び出す。
認証が成功した場合、取得した認証情報を SecurityContextHolder に設定するまでがお仕事。
SecurityContextHolder については後述)

認証処理ここまでのまとめ

AuthenticationManager を実装することはなさそう。デフォルトの実装クラスである ProviderManager を利用すればいいと思われる。
AuthenticationProvider は概ね所望する実装クラスが提供されていると思うので、適切に選択する。ただし WebAuthn など、まだ Spring Security がサポートしていない新しい認証方式を作りたいのであれば実装する必要がありそう。Filter についても同様。

ユーザの情報を取得する UserDetailsService の実装クラスは自分で作ることが多い。

認証情報の永続化

認証が完了して取得できた認証情報を永続化しておくことによって、認証済みであるかどうかを判定できるようになるし、ユーザ情報にもアクセスすることができる。

永続化.png

SecurityContext

認証情報(Authentication)を保持するオブジェクト。
認証情報をそのまま永続化するのではなく、この SecurityContext を永続化する。

SecurityContextRepository

SecurityContext を永続化する処理を行うためのインターフェース。
認証情報を永続化する場所としては HttpSession が思い浮かぶと思うが、Spring Security でも HttpSession を利用する実装クラスである HttpSessionSecurityContextRepository が提供されている。(デフォルトではそれを利用する)

SecurityContextHolder

SecurityContext を保持するクラス。デフォルトでは ThreadLocal を利用して保持する。
認証情報にアクセスする場合は、直接 HttpSession にアクセスするのではなく、この SecurityContextHolder から取り出すことができる。

SecurityContextPersistenceFilter

諸々の処理が始まる前に SecurityContextRepository から SecurityContext を取得し、SecurityContextHolder に設定する。
諸々の処理が終わった後に SecurityContextHolder から SecurityContext を取得し、SecurityContextRepository を利用して永続化を行い、SecurityContextHolder から SecurityContext を削除する。
SecurityContextHolderThreadLocal を利用しているが、この Filter が削除処理を行ってくれる。

永続化まとめ

認証情報は SecurityContext として永続化され、SecurityContextRepository によって永続化に関する処理が行われるが、あまり意識する必要はない。
SecurityContextHolder から SecurityContext を取得できるということを覚えておけばよさそう。

認証成功、失敗後の動作

認証処理を呼び出す Filter には認証成功、失敗時の動作を定義できるものもある。
例えば Form 認証を行う UsernamePasswordAuthenticationFilter には定義可能だし、Basic 認証を行う BasicAuthenticationFilter には定義できない。

AuthenticationSuccessHandler

認証成功.png

認証成功時の処理を定義する。
いくつか実装クラスが提供されているが、SavedRequestAwareAuthenticationSuccessHandler がデフォルトで利用される。
このクラスは RequestCache にリクエストが保持されている場合は、そのリクエストをリダイレクトする。
保持されていない場合は、設定されたデフォルトの URL にリダイレクトする。

未認証状態で認証が必要なページにアクセスし、認証画面にリダイレクトされる。
その後認証を完了させると、最初に表示しようとしていた画面へ遷移することができるのは、このクラスを利用しているため。

AuthenticationFailureHandler

認証失敗.png

認証失敗時の処理を定義する。
これもいくつか実装クラスが提供されているが、SimpleUrlAuthenticationFailuerHandler がデフォルトで利用される。
このクラスはいたってシンプルで、指定された URL に対してリダイレクトを行う。
また、認証失敗時に発生した例外を HttpSession に格納しておくことによって、リダイレクト先で参照が可能になる。

認証成功、失敗後の動作まとめ

認証成功、失敗後の動作を定義するにはぞれぞれ AuthenticationSuccessHandlerAuthenticationFailuerHandler を利用する。
提供されている実装クラスは指定された URL にリダイレクト(設定によってはフォワード)するだけだったりするので、高度なことをやりたいのであれば、実装クラスを作る必要がある。
また、認証方法によっては設定できないこともあるので注意が必要。

ログアウト

ついでにログアウトも。
ログアウト.png

LogoutFilter

ログアウト処理を行う LogoutHandler を呼び出したあと、後処理を行う LogoutSuccessHandler を呼び出す。
ログアウトを行う URL を定義できる。(デフォルトは /logout

LogoutHandler

ログアウト処理を行う。SecurityContextLogoutHandler では HttpSession を破棄している。
というか HttpSession に認証情報が格納されている前提なのか・・・。
SecurityContextRepository の実装次第では HttpSession に格納されているとは限らないと思うけど。でも削除用のメソッドないしなー。うーむ・・・。

なお、LogoutFilterCompositeLogoutHandler という複数の LogoutHanlder をまとめるクラスを利用しているため、必要に応じて複数の LogoutHandler を順番に実行できる。

LogoutSuccessHandler

ログアウト完了後の処理を行う。SimpleUrlLogoutSuccessHandler では指定された URL にリダイレクトする。

ログアウトまとめ

ログアウト処理をカスタマイズしたいのであれば、LogoutHandler を実装して LogoutFilter に設定すればいい。
ログアウト完了後の画面遷移などをカスタマイズしたいのであれば、LogoutSuccessHandler を実装して LogoutFilter に設定すればいい。

参考URL

さいごに

Spring Security なんかわかってきた気がする!(気のせい)

44
39
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
44
39