「Spring Security のカスタム認証」を作成していて、色々とハマってしまったので
SpringSecurityの認証について学んだことを備忘録として残します。
カスタム認証を使用した実装方法については別途記事を作成予定
自分がハマって調べたことだけなので、色々抜けている機能があることはご了承ください。
誤っている箇所や誤解を与えかねない表現があればご指摘いただけると助かります。
アーキテクチャ
- ブラウザからリクエストが送信されると「FilterChainProxy」がリクエストを受け取る
- FIlterChainProxyはFilterChainを呼び出す
- FilterChainには複数のFilterが登録されており、順番にFilterを実行し認証を行う。
フォーム認証
Filterには色々な種類がある(らしい)が、フォームにて入力された内容によって認証を行う「UsernamePasswordAuthenticationFilter」で説明
- UsernamePasswordAuthenticationFilterはリクエストから認証に必要な情報を取得する(デフォルトならuserNameとpassword)
- Authenticationに格納し、DaoAuthenticationProviderを起動する
- DaoAuthenticationProviderはUserDetailsServiceを使用し、ユーザ情報を取得し、認証を行う
- 認証がOKの場合、UsernamePasswordAuthenticationTokenを返却する
- 認証OKの場合(UsernamePasswordAuthenticationTokenが返却されている場合)successfulAuthenticationを実行する
- セキュリティコンテキスト(認証情報等)を保存
AnonymousAuthenticationFilter
フィルターチェーンの最後(厳密には最後じゃないかも?)にAnonymousAuthenticationFilterが起動される。
認証されていないユーザーに対して匿名認証を提供する。
つまり、こいつが最終的に認証がOKだったかの確認を行い、認証されていなければNG扱いにするということ
AnonymousAuthenticationFilterは「ThreadLocalSecurityContextHolderStrategy」から認証情報を取得し、認証情報が存在すればOKと判断している。
ハマったポイント
AnonymousAuthenticationFilterはスレッドローカルから認証情報を取得しているので、異なるスレッドの場合は情報は引き継がれない(認証OKとはならない)
実際に認証NGとなってしまった時のログを見るとUsernamePasswordAuthenticationFilterが認証OKとしているスレッドとAnonymousAuthenticationFilterがNG(匿名認証をセット)としているスレッドが違った
※[nio-8080-exec-4]
赤字部分がスレッド
DEBUG [nio-8080-exec-4]com.example.auth.SampleFilter Set SecurityContextHolder to UsernamePasswordAuthenticationToken
省略
DEBUG [nio-8080-exec-6]o.s.s.w.a.AnonymousAuthenticationFilter Set SecurityContextHolder to anonymous SecurityContext
デフォルトでは、UsernamePasswordAuthenticationFilter(厳密にいうとAbstractAuthenticationProcessingFilter)は「RequestAttributeSecurityContextRepository」を使用しており、これはスレッド間の共有ができない。
private SecurityContextRepository securityContextRepository = new RequestAttributeSecurityContextRepository();
そのため、セッションに保存して異なるスレッド間でも共有できるようにする必要がある。
セッションに保存するためには「HttpSessionSecurityContextRepository」を使用する
「デフォルトでは」と記載しているが、SpringSecurityがデフォルトで登録しているUsernamePasswordAuthenticationFilterは「HttpSessionSecurityContextRepository」が別途設定されている。
ややっこしい・・・