読者です 読者をやめる 読者になる 読者になる

M12i.

学術書・マンガ・アニメ・映画の消費活動とプログラミングについて

Spring Security 3.1 リファレンス - テクニカル・サマリ (2)

Spring Security 3.1 リファレンスマニュアルの“Technical Overview”のチャプターを訳出しています。今回は後半部分。前半部分はこちら

原典は、“6. Technical Overview”(Spring Security Reference Documentation)で、Ben Alex氏とLuke Taylor氏によるものです。

* * *

テクニカル・サマリ (2)

6.4 Webアプリケーションにおける認証

さて、ここではWebアプリケーションで(web.xmlでのセキュリティ設定なしに)Spring Securityを使用するシチュエーションを見ていきましょう。どのようにユーザを認証し、セキュリティ・コンテキストを確立するのでしょうか?
Webアプリケーションの典型的な認証プロセスについて考えましょう:

  1. あなたはホームページを訪れ、リンクをクリックします。
  2. リクエストがサーバに送信され、あなたが保護されたリソースへの問い合わせを行ったと判断します。
  3. 目下のところあなたはまだ認証を済ませていないので、サーバはあなたが認証をしなくてはならない旨の返答を返します。これはHTTPレスポンス・コードによるものか、あるいは特定のWebページへのリダイレクトによるものかも知れません。
  4. 認証機構によりけりですが、あなたのブラウザは入力フォームのあるWebページにリダイレクトしたり、(BASIC認証ダイアログボックスや、クッキー情報、X.509証明書などから)何かしらの方法であなたのアイデンティティ情報を取得したりします。
  5. ブラウザはサーバに返答を送信します。これはあなたが入力したフォーム情報を含むHTTP POSTリクエストによるものかも知れませんし、HTTPヘッダーに認証情報を記載する方法によるものかも知れません。
  6. 続いてサーバ側で認証情報が正しいものであるか否かが判定されます。もし正しいものであれば、次のステップに進みます。不正なものであれば、たいていの場合ブラウザはあなたに再度認証情報の入力を促すでしょう。(2つ前のステップからやり直しです。)
  7. 認証プロセスが始まるもととなったそもそものリクエスト内容が再取得されます。うまくいけばあなたは保護されたリソースにアクセスするために十分な権限を付与されているはずです。もしアクセスできたならば、リクエストは成功です。さもなくば、HTTPエラー・コード403──“禁じられている”〔権限がない、不足している〕ことを意味するコード──を受け取ることになるでしょう。

Spring Securityでは上述の各ステップのほとんどで関与するクラス群を互いに区別しています。主に関与するのは(それらが使用される順序にしたがって)ExceptionTranslationFilter、AuthenticationEntryPoint、そして“認証機構”──これは前の節で言及したAuthenticationManagerを呼び出すもの──です。

6.4.1 ExceptionTranslationFilter

ExceptionTranslationFilterはSpring Securityが提供するフィルタで、〔認証プロセスの中で〕スローされるSpring Security例外を検知するものです。これらの例外はふつう、認証サービスの主な提供者となるAbstractSecurityInterceptorの実装によりスローされます。AbstractSecurityInterceptorについては次節で取り上げるとして、ここでは、それがJava例外を生成するということ、そしてまた一方ではそれがHTTPやプリンシパルの認証方法について何ら関知しないことを理解していれば十分です。後者についてはExceptionTranslationFilterの仕事です。フィルタは、エラー・コード403を返すか(プリンシパルがすでに認証されているものの、アクセス権限が十分でないケース──前述のステップ7で言及されているもの)、そうでなければAuthenticationEntryPointを起動します(プリンシパルはまだ認証されていないので、前述のステップ3を開始する必要があります)。

6.4.2 AuthenticationEntryPoint

AuthenticationEntryPointは前述のステップ3を担当します。ご想像できることと思いますが、それぞれのWebアプリケーションにはデフォルトの認証戦略があります。(もちろんSpring Securityではその他のほとんどすべての事項についてカスタマイズができますが、ここでの説明は簡潔にしておきましょう。)それぞれのメジャーな認証システムに対応したAuthenticationEntryPoint実装が用意されています。これらはたいていステップ3で言及した種々のアクションのうちどれかを実行します。

6.4.3 認証メカニズム

ブラウザがあなたの認証情報を(フォーム入力内容のPOST送信やHTTPヘッダ情報によって)送信すると、サーバ側では認証情報をかき集めて何ごとかする必要があります。いま私たちは前述のステップのなかの6番目にいます。Spring Securityでは、ユーザ・エージェント(いわゆるWebブラウザ)から認証情報を受け取る機能に“認証メカニズム”〔authentication mechanism〕という特別な名前を付けています。例えば、フォーム・ベースの認証とBASIC認証ですが、まずユーザ・エージェントから送られた認証データを受け取ると、Authentication“リクエスト”オブジェクトを生成して、AuthenticationManagerに渡します。

もしリクエストが正しいものであれば、十全な情報を設定されたAuthenticationオブジェクトが返却されます。認証メカニズムは、受け取ったAuthenticationオブジェクトをSecurityContextHolderのなかに格納します。そしてその状態でもともとのリクエストが再試行させます。(ステップ7です。)

6.4.4 リクエスト間でSecurityContextを保存する

アプリケーションタイプによりけりですが、セキュリティ・コンテキストをユーザの操作と操作の間で保存しておく戦略が必要になることがあります。典型的なWebアプリケーションでは、ユーザが一度ログインすると、以後はセッションIDによって識別されるようになります。セッションの継続中、サーバはプリンシパルの情報を保持しつづけます。Spring Securityでは、SecurityContextをリクエストとリクエストの間で保存しておく役割はSecurityContextPersistenceFilterに期待されています。このインターフェースのデフォルト実装は、あるHTTPリクエストと続くそれの間、HttpSession の属性としてセキュリティ・コンテキストを保存します。フィルタはリクエストのたびSecurityContextHolderの中にSecurityContextを復元し、そして──ここが重要なところなのですが──リクエストが完了するとSecurityContextHolderの内容はクリアされます。セキュリティのためにHttpSessionオブジェクトに直接働きかけてはいけません。そのようなことをする理由はどこにもありません。いつでもSecurityContextHolderを介してセキュリティ・コンテキストにアクセスしてください。

その他の多くのアプリケーションでは(例えば、ステートレスなRESTful Webサービスとか)、HTTPセッションは使用せず、リクエストのあるごとに再度認証を行うでしょう。この場合にも依然として重要なことは、リクエストのたび確実にSecurityContextHolderをクリアする処理の連鎖のなかにSecurityContextPersistenceFilterが組み込まれていることです。

ノート

ひとつのセッションのなかで並列リクエストを受け取るアプリケーションの場合、同じSecurityContextインスタンスがスレッド間で共有されます。ここでもThreadLocalは使用されていますが、これはそれぞれのスレッドのためにHttpSessionから取得された同一のインスタンスです。このことから、もしあなたがセキュリティ・コンテキストを実行中のスレッドから一時的に変更しようと考えているとき、どのようなことがおこるでしょうか。もしあなたがSecurityContextHolder.getContext()メソッドを使用し、返却されたコンテキストのsetAuthentication(anAuthentication)メソッドをコールしたとき、Authenticationオブジェクトは、同じSecurityContextインスタンスを共有するすべての並列スレッドにおいて変化します。これに対して、あるスレッドでの変更がほかに波及するのを防ぐため、リクエストのたびに完全に新たなSecurityContextを生成するようSecurityContextPersistenceFilterをカスタマイズすることもできます。また別の方法としては、一時的にコンテキストを変更するまさにその場で、〔共有された既存のインスタンスを変更するのではなく〕新規インスタンスを作成することもできます。SecurityContextHolder.createEmptyContext()メソッドはいつでも新しいコンテキスト・インスタンスを返します。

6.5 Spring Securityにおけるアクセス制御(権限付与)

Spring Securityにおいてアクセス制御判定を司る中心的インターフェースはAccessDecisionManagerです。このインターフェースが持つdecideメソッドは、リソースへのアクセスを要求するプリンシパル〔ユーザ〕を表すAuthentication、“保護されたオブジェクト”〔secure object〕(後述)、そしてそのオブジェクトに適用されるセキュリティに関するメタデータの属性のリスト(例えばアクセス権が付与される条件となるロールのリストのような)を引数にとります。

6.5.1 セキュリティとAOPアドバイス

もしAOPを使ったことがあるなら、使用可能な“アドバイス”の、互いに異なるいくつかの型についてもご存じでしょう:“ビフォア”〔before〕、“アフター”〔after〕、“スロウズ”〔throws〕、そして“アラウンド”〔around〕です。なかでもメソッドの実行の如何、返却値の修正の如何、そして例外スロー実施の如何までも制御できるために、アラウンド・アドバイスはもっとも使いやすいアドバイスです。Spring Securityではメソッド実行だけでなくWebリクエストのためにもこのアラウンド・アドバイスが提供されています。Spring標準のAOPサポートによりメソッド実行に対してアラウンド・アドバイスを適用することも、標準のフィルタを用いてWebリクエストに対してアラウンド・アドバイスを適用することもできるのです。〔つまり、ユーザの認証状態と権限付与状態に基づきメソッド実行の可否等を制御したり、特定のWebサイト・リソースへのアクセスを制御したりできる、ということ。〕

AOPを使ったことのない方にとっての理解の鍵は、Spring Securityはメソッド実行だけでなく、Webリクエストもまた保護することもまたできる〔ユーザの認証状態・権限付与状態に基づいてその操作の可否を制御できる〕、ということです。ほとんどの人はサービス・レイヤにおける保護されたメソッド実行について興味があるでしょう。これはサービス・レイヤが、今日のJ2EEアプリケーションにおいてビジネス・ロジックがもっぱら存在する場所であるからです。もしサービス・レイヤにおいて保護されたメソッド実行が必要なら、Springの標準AOPが適当でしょう。またもし直接に保護されたドメイン・オブジェクトが必要なら、AspectJの使用も検討してみるべきでしょう。

あなたはAspectJもしくはSpring AOPを用いることでメソッド権限制御を行ったり、フィルタを用いてWebリクエストの権限制御を行ったりすることができます。いずれのアプローチも用いないこともあれば、1つ、2つ、あるいは3つのアプローチを組み合わせ用いることもあるでしょう。Webリクエスト権限制御を実行する場合、主流となっているのはSpring AOPによるサービス・レイヤにおけるメソッド実行権限制御を組み合わせて使う方法です。

6.5.2 保護されたオブジェクトとAbstractSecurityInterceptor

そもそも“保護されたオブジェクト”〔secure object〕とは一体何なのでしょうか? Spring Securityではこの言葉を、セキュリティ(権限判定のような)が適用されたあらゆるオブジェクトを指すものとして使用しています。もっとも一般的な例としてはメソッド実行やWebリクエストです。

いずれの保護されたオブジェクトのタイプも、AbstractSecurityInterceptorのサブクラスとしてそれぞれに特化したインターセプター・クラスを持っています。重要なことは、AbstractSecurityInterceptorがコールされたとき、SecurityContextHolderにはプリンシパルがすでに認証されていれば正しいAuthentication オブジェクトが格納されているということです。

AbstractSecurityInterceptorは保護されたオブジェクトへのアクセス要求に対して一貫したワークフローを提供します。典型的なフローは:

  1. 現在のリクエストに紐づけられた設定属性〔configuration attributes〕を調べます。
  2. 権限判定のため、保護されたオブジェクト、現在のAuthenticationオブジェクト、そして設定属性をAccessDecisionManagerに渡します。
  3. 必要に応じてAuthenticationオブジェクトの内容が変更されます。
  4. 保護されたオブジェクトへのアクセスが許可されます。(権限が付与されるなど。)
  5. アクセスが終わると、もしあらかじめ設定されていればAfterInvocationManagerがコールされます。もし保護されたオブジェクトへのアクセス中に例外がスローされた場合には、AfterInvocationManagerはコールされません。

〔訳注:ここでは“オブジェクト”と“インヴォケーション”という語が特殊な意味で用いられています。“オブジェクト”はJavaクラスやインスタンスのことを指しているのではなく、ましてや現実世界における“もの”を表しているのでもありません。あるユーザの操作、あるクライアントからのリクエストの対象(目的)となる機能やリソースなどを全般的に指すための抽象的な概念です。“インヴォケーション”はそうした“オブジェクト”にアクセスし、使用し、実行することを指す抽象的な概念です。〕

設定属性とは何か?

“設定属性”〔configuration attribute〕はAbstractSecurityInterceptor で使用されるクラス中で特殊な意味を持った文字列のことです。フレームワークのなかでこれらの情報はConfigAttribute インターフェースにより表されます。それは単純なロール名のこともあるでしょうし、AccessDecisionManagerの実装の高度さに応じてもっと複雑な意味を持つ情報のこともあるでしょう。AbstractSecurityInterceptor はSecurityMetadataSource ──SecurityMetadataSourceは保護されたオブジェクトについての属性情報を参照するのに使用──とともに事前に設定されます。ふつうこの設定情報はユーザからは秘匿されます。設定属性は保護されたメソッドへのアノテーションによって定義されることもあれば、保護されたURLへのアクセス属性として定義されることもあります。例えば、名前空間の導入の解説で触れた <intercept-url pattern='/secure/**' access='ROLE_A,ROLE_B'/> のようなものですが、ここでは指定されたパターンにマッチするWebリソースへのリクエストに、ROLE_AとROLE_Bが適用されています。これは、デフォルトのAccessDecisionManager 実装の設定では、2つの属性値のうちいずれかに該当するGrantedAuthority を持っているユーザは誰であれ、このURLにアクセスできるということを意味します。とはいえ厳密に言えば、個々の属性値〔その取り得る値〕と解釈はすべてAccessDecisionManagerの実装次第です。ROLE_接頭辞はそれらがロールであること、そしてSpring SecurityのRoleVoterオブジェクトにより処理されるものであることを示すマーカです。これはVoterベースのAccessDecisionManager実装を使用するときにのみ関連してくる事項です。権限付与のチャプターでは、AccessDecisionManagerがどのように実装されているかを知ることができます。

RunAsManager

AccessDecisionManagerがリクエストを許可すると、AbstractSecurityInterceptorはたいていリクエストの処理をそのまま進めます。これに関して稀なケースではありますが、SecurityContextに格納されたAuthenticationインスタンスを別のインスタンスに置き換える必要があることもあるでしょう。これはAccessDecisionManagerがRunAsManagerをコールすることで処理されます。サービス・レイヤのメソッドがリモート・システムを呼び出す際に異なるユーザ同定情報を渡す必要があるというような、特別な理由のある状況では便利です。Spring Securityはセキュリティ・アイデンティティをあるサーバから別のサーバへと自動的に伝搬させるため(適切に事前設定されたRMI〔遠隔メソッド実行〕やHttpInvokerの遠隔プロトコル・クライアントの使用を想定しています)、この仕組みは役立つはずです。

AfterInvocationManager

保護されたオブジェクトの実行へと進み、それが終わった後──つまりメソッド実行が完了するかフィルタ・チェーンが進むかしたとき──AbstractSecurityInterceptorは実行制御のための最後の機会を得ることになります。このステージで、AbstractSecurityInterceptorは返却されるオブジェクトに対する修正を企てることができます。権限付与の判定は保護されたオブジェクトの実行の途中で行うこと〔つまり、より細かい単位での権限制御を行うこと〕ができないことからこうした処理が必要になることがあります。高度な拡張性を実現するため、AbstractSecurityInterceptorは必要に応じて対象を修正する処理をAfterInvocationManagerに委譲します。このクラスは対象オブジェクトを完全に置き換えてしまったり、例外をスローしたり、あるいはいかなる変更も加えずおいたりすることができます。実行後のチェックは保護されたオブジェクトの実行が成功したときにのみ行われます。もし例外が発生したら、このチェックはスキップされます。

AbstractSecurityInterceptorとこれに関連するオブジェクトは、図6.1 「セキュリティ・インターセプタと“保護されたオブジェクト”のモデル」に示される通りです。

保護されたオブジェクトのモデルを拡張する

リクエストをインターセプトし、権限付与する方法をまったく新たに実装することを検討している開発者の方は、この保護されたオブジェクトのモデルを直接使用する必要があるでしょう。例えば、メッセージング・システムのための安全な〔メソッドやサービスの〕呼び出しを行う保護されたオブジェクトを新しく構築するといったことが可能でしょう。およそセキュリティを要求し、(AOPのアラウンド・アドバイスが動作するように)メソッド呼び出しをインターセプトする方法を提供するものは、いずれも保護されたオブジェクトになることができます。とはいえ、ほとんどのSpringアプリケーションは単純に既存の保護されたオブジェクトの型(AOPアライアンスのMethodInvocation、AspectJのJoinPoint、そしてWebリクエストのFilterInvocation)を完全な透過性のもとで使用しています。

6.6 ローカリゼーション

Spring Securityは、エンド・ユーザが目にすることになる例外発生時のメッセージについて、ローカライズする手段を提供しています。デフォルトのSpring Securityのメッセージはすべて英語で書かれているので、もしあなたのアプリケーションが英語話者のユーザのために設計されているのであれば、特段何もする必要はありません。しかしもし他のロケールをサポートしなくてはならないなら、このセクションの内容を読んでおく必要があります。

認証が失敗した場合やアクセスが拒否された場合(権限付与が失敗した場合)のメッセージを含め、すべての例外メッセージはローカライズできます。アプリケーションの開発者やシステム配備者向けの(不正な属性値、インターフェースの型不正、不正なコンストラクタ使用、バリデーションの開始時間、デバッグ・レベルのログ出力といったもの)例外とログ・メッセージはSpring Securityのコード内にハードコードされており、ローカライズされません。

spring-security-core-xx.jarのなかのorg.springframework.securityパッケージにはいくつかの共通語にローカライズされたmessages.propertiesファイルが含まれています。これらのプロパティ・ファイルはApplicationContextから参照されることになる必要があります。Spring Securityのクラス群はSpringのMessageSourceAwareインターフェースを実装しており、アプリケーション・コンテキストの起動時、メッセージ・リゾルバ〔message resolver〕が依存性注入されることを想定しているためです。たいていの場合、あなたがしなくてはならないのは、アプリケーション・コンテキストのなかに、メッセージのプロパティ・ファイルを参照するためのビーン〔bean〕を登録しておくことだけです。以下に例を示します:

<bean id="messageSource"
     class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
   <property name="basename" value="classpath:org/springframework/security/messages"/> 
</bean> 

messages.propertiesは、リソース・バンドルの標準にしたがって命名され、Spring Securityにより提供されたデフォルトのメッセージを表します。デフォルトのファイルは英語です。
messages.propertiesファイルをカスタマイズしたり、他の言語でメッセージを提供したりする場合、このファイルをコピーしたあとリネームし、上述のビーン定義で登録することになります。ファイルに列挙されたメッセージはそう多くはありませんから、それをローカライズすることは別に大それた試みではありません。もしこのファイルを翻訳された方がいらっしゃったら、ローカライズ版のmessages.propertiesを添付してJIRAタスクを登録しユーザ・コミュニティ内で共有することをぜひ検討してみてください。

Spring Securityは適切なバージョンのメッセージ・ファイルを見つけるためにSpringのローカライゼーション・サポート機能に依存しています。この仕組みがうまく機能するように、やってきたリクエストのロケールがSpringフレームワークのorg.springframework.context.i18n.LocaleContextHolderの中に格納されていることを確認する必要があります。Spring MVCのDispatcherServletはこれを自動的に行ってくれるのですが、Spring Securityのフィルタ群はそれが行われるより前に実行されます。このためSpring Securityのフィルタ群が実行されるよりも前にLocaleContextHolderに対して適切なLocaleが設定されている必要があります。これはあなた自身が実装するフィルタのなかで行ってもいいですし(この場合、web.xmlの中であなたのフィルタはSpring Securityのそれよりも前に記述されている必要があります)、SpringフレームワークのRequestContextFilterを使ってもいいです。Springフレームワークにおいてローカリゼーション機能の使用に関してより詳細な情報は、Springフレームワークのドキュメントを参照してください。

サンプル・アプリケーションの“contacts”では、ローカライズされたメッセージが使用されています。

* * *

前半部分はこちら

原典は、“6. Technical Overview”(Spring Security Reference Documentation)で、Ben Alex氏とLuke Taylor氏によるものです。


Spring関連の訳出記事まとめ - M12i.