Spring Security FAQ

一般的な質問

この FAQ は、次の一般的な質問に回答します。

Spring Security はすべてのアプリケーションセキュリティ要件を処理できますか?

Spring Security は、認証と認可の要件に柔軟なフレームワークを提供しますが、その範囲外の安全なアプリケーションを構築するための考慮事項は他にもたくさんあります。Web アプリケーションは、最初から念頭に置いて設計およびコーディングできるように、できれば開発を開始する前に、よく知っている必要のあるあらゆる種類の攻撃に対して脆弱です。Web アプリケーション開発者が直面する主要な課題と、それらに対して使用できる対策については、OWASP Web サイト (英語) を確認してください。

web.xml セキュリティを使用しないのはなぜですか?

Spring に基づくエンタープライズアプリケーションを開発していると仮定します。通常、認証、Web リクエストセキュリティ、サービス層セキュリティ(ビジネスロジックを実装するメソッド)、およびドメインオブジェクトインスタンスセキュリティ(異なるドメインオブジェクトが異なる権限を持つことができる)の 4 つのセキュリティ問題に対処する必要があります。これらの一般的な要件を念頭に置いて、次の考慮事項があります。

  • 認証 : サーブレット仕様は、認証へのアプローチを提供します。ただし、認証を実行するようにコンテナーを構成する必要があります。これには通常、コンテナー固有の「レルム」設定の編集が必要です。これにより、移植性のない構成になります。また、コンテナーの認証インターフェースを実装するために実際の Java クラスを作成する必要がある場合は、さらに移植性が低くなります。Spring Security を使用すると、WAR レベルに至るまで完全な移植性を実現できます。また、Spring Security は、本番で実証済みの認証プロバイダーとメカニズムの選択肢を提供します。つまり、デプロイ時に認証アプローチを切り替えることができます。これは、未知のターゲット環境で動作する必要のある製品を作成しているソフトウェアベンダーにとって特に価値があります。

  • Web リクエストのセキュリティ : サーブレット仕様は、リクエスト URI を保護するためのアプローチを提供します。ただし、これらの URI は、サーブレット仕様独自の限定された URI パス形式でのみ表現できます。Spring Security は、はるかに包括的なアプローチを提供します。たとえば、Ant パスまたは正規表現を使用したり、リクエストされたページ以外の URI の一部を検討したり(たとえば、HTTP GET パラメーターを検討したり)、構成データの独自のランタイムソースを実装できます。これは、Web アプリケーションの実際の実行中に Web リクエストのセキュリティを動的に変更できることを意味します。

  • サービス層とドメインオブジェクトのセキュリティ : サービス層のセキュリティまたはドメインオブジェクトインスタンスのセキュリティのサーブレット仕様にサポートがないことは、多層アプリケーションの重大な制限を表しています。通常、開発者はこれらの要件を無視するか、MVC コントローラーコード内(またはさらに悪いことに、ビュー内)にセキュリティロジックを実装します。このアプローチには重大な欠点があります。

    • 関心事の分離 : 認可は横断的関心事であり、そのように実装する必要があります。認可コードを実装する MVC コントローラーまたはビューは、コントローラーと認可ロジックの両方のテストをより困難にし、デバッグをより困難にし、多くの場合、コードの重複を引き起こします。

    • リッチクライアントと Web サービスのサポート : 最終的に追加のクライアント型をサポートする必要がある場合、Web レイヤーに埋め込まれている認証コードは再利用できません。Spring リモーティングエクスポーターはサービスレイヤー Bean のみをエクスポートする(MVC コントローラーはエクスポートしない)ことを考慮する必要があります。その結果、多数のクライアント型をサポートするには、認可ロジックをサービスレイヤーに配置する必要があります。

    • レイヤーリングの課題 : MVC コントローラーまたはビューは、サービスレイヤーメソッドまたはドメインオブジェクトインスタンスに関する認可決定を実装するための誤ったアーキテクチャーレイヤーです。プリンシパルをサービスレイヤーに渡して認可を決定できるようにすることもできますが、そうすると、すべてのサービスレイヤーメソッドに追加の引数が導入されます。より洗練されたアプローチは、ThreadLocal を使用してプリンシパルを保持することですが、これにより、専用のセキュリティフレームワークを使用する方が(費用便益ベースで)より経済的になるまで開発時間が長くなる可能性があります。

    • 認可コードの品質 : Web フレームワークについては、「正しいことを実行しやすくし、間違ったことを実行しにくくする」とよく言われます。セキュリティフレームワークは、さまざまな目的のために抽象的な方法で設計されているため、同じです。独自の認証コードを最初から作成しても、フレームワークが提供する「設計チェック」は提供されません。また、社内の認証コードには、通常、広範なデプロイ、ピアレビュー、新しいバージョンから生じる改善が欠落しています。

単純なアプリケーションの場合、サーブレット仕様のセキュリティで十分な場合があります。Web コンテナーの移植性、構成要件、制限された Web リクエストのセキュリティの柔軟性、および存在しないサービス層とドメインオブジェクトインスタンスのセキュリティのコンテキスト内で検討すると、開発者が代替ソリューションを探すことが多い理由が明らかになります。

どの Java および Spring Framework バージョンが必要ですか?

Spring Security 3.0 および 3.1 には、少なくとも JDK 1.5 が必要であり、少なくとも Spring 3.0.3 も必要です。理想的には、問題を回避するために最新のリリースバージョンを使用する必要があります。

Spring Security 2.0.x には、1.4 の最小 JDK バージョンが必要であり、Spring 2.0.x に対して構築されています。また、Spring 2.5.x を使用するアプリケーションとも互換性がある必要があります。

複雑なシナリオがあります。何が間違っている可能性がありますか?

(この回答は、特定のシナリオを扱うことにより、一般的な複雑なシナリオに対応しています)

Spring Security を初めて使用し、HTTPS を介した CAS シングルサインオンをサポートすると同時に、特定の URL に対してローカルで基本認証を許可し、複数のバックエンドユーザー情報ソース(LDAP および JDBC)に対して認証するアプリケーションを構築する必要があるとします。いくつかの構成ファイルをコピーしましたが、機能しないことがわかりました。何が間違っている可能性がありますか?

それらを使用してアプリケーションを正常に構築する前に、使用する予定のテクノロジーを理解する必要があります。セキュリティは複雑です。ログインフォームと Spring Security の名前空間を持つ一部のハードコードされたユーザーを使用して簡単な構成を設定するのはかなり簡単です。裏付けのある JDBC データベースの使用への移行も簡単です。ただし、このシナリオのような複雑なデプロイシナリオに直接ジャンプしようとすると、フラストレーションを感じることはほぼ間違いありません。CAS などのシステムをセットアップし、LDAP サーバーを構成し、SSL 証明書を適切にインストールするために必要な学習曲線には、大きな飛躍があります。一度に一歩ずつ物事を進める必要があります。

Spring Security の観点から見ると、最初に行うべきことは、Web サイトの「入門」ガイドに従うことです。これにより、一連の手順を実行して起動して実行し、フレームワークがどのように動作するかを理解できるようになります。馴染みのない他のテクノロジを使用する場合は、複雑なシステムに組み合わせる前に、いくつかの調査を行って、単独で使用できることを確認する必要があります。

よくある問題

このセクションでは、Spring Security を使用するときに人々が遭遇する最も一般的な問題について説明します。

ログインしようとすると、"BadCredentials" というエラーメッセージが表示されます。なにが問題ですか?

これは、認証が失敗したことを意味します。攻撃者がアカウント名やパスワードを推測するのに役立つ可能性のある詳細を提供することは避けるのが良い習慣であるため、理由はわかりません。

これは、オンラインでこの質問をした場合、追加情報を提供しない限り回答を期待できないことも意味します。他の課題と同様に、デバッグログの出力を確認し、例外スタックトレースと関連メッセージを記録する必要があります。デバッガーでコードをステップ実行して、認証が失敗する場所とその理由を確認する必要があります。アプリケーションの外部で認証構成を実行するテストケースも作成する必要があります。ハッシュされたパスワードを使用する場合は、データベースに保存されている値が、アプリケーションで構成されている PasswordEncoder によって生成される値とまったく同じであることを確認してください。

ログインしようとすると、アプリケーションが「無限ループ」に入ります。何が起こっているのでしょうか ?

無限ループとログインページへのリダイレクトに関する一般的なユーザーの問題は、ログインページを「保護された」リソースとして誤って構成したことが原因で発生します。構成でログインページへの匿名アクセスが許可されていることを確認してください。セキュリティフィルターチェーンから除外するか、ROLE_ANONYMOUS が必要であるとマークしてください。

AccessDecisionManager に AuthenticatedVoter が含まれている場合は、IS_AUTHENTICATED_ANONYMOUSLY 属性を使用できます。これは、標準の名前空間構成セットアップを使用する場合に自動的に使用可能になります。

Spring Security 2.0.1 以降、名前空間ベースの構成を使用すると、アプリケーションコンテキストのロードがチェックされ、ログインページが保護されているように見える場合は警告メッセージがログに記録されます。

「アクセスが拒否されました(ユーザーは匿名です);」というメッセージで例外が発生します。どうしてでしょうか?

これは、匿名ユーザーが保護されたリソースに初めてアクセスしようとしたときに発生するデバッグレベルのメッセージです。

DEBUG [ExceptionTranslationFilter] - Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.AccessDeniedException: Access is denied
at org.springframework.security.vote.AffirmativeBased.decide(AffirmativeBased.java:68)
at org.springframework.security.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:262)

これは正常なことであり、心配する必要はありません。

アプリケーションからログアウトした後でも、セキュリティで保護されたページが表示されるのはなぜですか?

この最も一般的な理由は、ブラウザーがページをキャッシュしており、ブラウザーのキャッシュから取得されたコピーが表示されていることです。これを確認するには、ブラウザーが実際にリクエストを送信しているかどうかを確認します (サーバーのアクセスログとデバッグログを確認するか、Firefox の "Tamper Data" などの適切なブラウザーデバッグプラグインを使用します)。これは Spring Security とは関係がないため、適切な Cache-Control レスポンスヘッダーを設定するようにアプリケーションまたはサーバーを構成する必要があります。SSL リクエストは決してキャッシュされないことに注意してください。

「SecurityContext で認証オブジェクトが見つかりませんでした」というメッセージで例外が発生します。なにが問題ですか?

次のリストは、匿名ユーザーが保護されたリソースに初めてアクセスしようとしたときに発生する別のデバッグレベルのメッセージを示しています。ただし、このリストは、フィルターチェーン構成に AnonymousAuthenticationFilter がない場合に何が起こるかを示しています。

DEBUG [ExceptionTranslationFilter] - Authentication exception occurred; redirecting to authentication entry point
org.springframework.security.AuthenticationCredentialsNotFoundException:
							An Authentication object was not found in the SecurityContext
at org.springframework.security.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:342)
at org.springframework.security.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:254)

これは正常であり、心配する必要はありません。

LDAP 認証が機能しません。構成の何が問題になっていますか?

LDAP ディレクトリのアクセス許可では、多くの場合、ユーザーのパスワードを読み取れないことに注意してください。Spring Security が保存されたパスワードをユーザーが送信したパスワードと比較する UserDetailsService とは何ですか? を使用できないことがよくあります。最も一般的なアプローチは、LDAP プロトコル [Wikipedia] でサポートされている操作の 1 つである LDAP「バインド」を使用することです。このアプローチでは、Spring Security は、ユーザーとしてディレクトリへの認証を試みることにより、パスワードを検証します。

LDAP 認証の最も一般的な問題は、ディレクトリサーバーのツリー構造と構成に関する知識が不足していることです。これは会社によって異なるため、自分で調べなければなりません。Spring Security LDAP 構成をアプリケーションに追加する前に、標準の Java LDAP コード(Spring Security を含まない)を使用して簡単なテストを作成し、それが最初に機能することを確認する必要があります。例: ユーザーを認証するには、次のコードを使用できます。

  • Java

  • Kotlin

@Test
public void ldapAuthenticationIsSuccessful() throws Exception {
		Hashtable<String,String> env = new Hashtable<String,String>();
		env.put(Context.SECURITY_AUTHENTICATION, "simple");
		env.put(Context.SECURITY_PRINCIPAL, "cn=joe,ou=users,dc=mycompany,dc=com");
		env.put(Context.PROVIDER_URL, "ldap://mycompany.com:389/dc=mycompany,dc=com");
		env.put(Context.SECURITY_CREDENTIALS, "joespassword");
		env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");

		InitialLdapContext ctx = new InitialLdapContext(env, null);

}
@Test
fun ldapAuthenticationIsSuccessful() {
    val env = Hashtable<String, String>()
    env[Context.SECURITY_AUTHENTICATION] = "simple"
    env[Context.SECURITY_PRINCIPAL] = "cn=joe,ou=users,dc=mycompany,dc=com"
    env[Context.PROVIDER_URL] = "ldap://mycompany.com:389/dc=mycompany,dc=com"
    env[Context.SECURITY_CREDENTIALS] = "joespassword"
    env[Context.INITIAL_CONTEXT_FACTORY] = "com.sun.jndi.ldap.LdapCtxFactory"
    val ctx = InitialLdapContext(env, null)
}

セッション管理

セッション管理の課題は、よくある質問の原因です。Java Web アプリケーションを開発している場合は、サーブレットコンテナーとユーザーのブラウザーの間でセッションがどのように維持されるかを理解する必要があります。また、セキュア Cookie と非セキュア Cookie の違い、および HTTP と HTTPS を使用して 2 つを切り替えることの意味を理解する必要があります。Spring Security は、セッションの維持やセッション ID の提供とは何の関係もありません。これは完全にサーブレットコンテナーによって処理されます。

Spring Security の同時セッション制御を使用して、ユーザーが同時に複数回ログインできないようにしています。ログイン後に別のブラウザーウィンドウを開いても、再度ログインできます。なぜ複数回ログインできるのですか?

ブラウザーは通常、ブラウザーインスタンスごとに 1 つのセッションを維持します。一度に 2 つの別々のセッションを持つことはできません。別のウィンドウまたはタブで再度ログインすると、同じセッションで再認証するだけです。別のウィンドウまたはタブで再度ログインすると、同じセッションで再認証されます。サーバーは、タブ、ウィンドウ、ブラウザーインスタンスについて何も知りません。表示されるのは HTTP リクエストだけであり、含まれている JSESSIONID Cookie の値に従って特定のセッションに関連付けられます。ユーザーがセッション中に認証を行うと、Spring Security の同時セッション制御は、ユーザーが持っている他の認証済みセッションの数をチェックします。それらが同じセッションですでに認証されている場合、再認証は効果がありません。

Spring Security で認証すると、セッション ID が変わるのはなぜですか?

デフォルト設定では、Spring Security はユーザーの認証時にセッション ID を変更します。Servlet 3.1 以降のコンテナーを使用する場合、セッション ID は単純に変更されます。古いコンテナーを使用する場合、Spring Security は既存のセッションを無効にし、新しいセッションを作成し、セッションデータを新しいセッションに転送します。この方法でセッション識別子を変更すると、「セッション固定」攻撃が防止されます。詳細については、オンラインおよびリファレンスマニュアルを参照してください。

Tomcat (または他のサーブレットコンテナー) を使用し、ログインページで HTTPS を有効にし、その後 HTTP に戻します。それは動作しません。認証後、ログインページに戻ります。

効かない - 認証後、ログインページに戻ります。

これは、セッション Cookie が「安全」とマークされている HTTPS で作成されたセッションは、その後 HTTP では使用できないために発生します。ブラウザーは Cookie をサーバーに返送せず、セッション状態(セキュリティコンテキスト情報を含む)は失われます。セッション Cookie は安全であるとマークされていないため、最初に HTTP でセッションを開始すると機能するはずです。ただし、Spring Security のセッション固定保護は、通常はセキュアフラグを使用して、新しいセッション ID Cookie がユーザーのブラウザーに返送されるため、これを妨げる可能性があります。これを回避するために、セッション固定保護を無効にすることができます。ただし、新しいサーブレットコンテナーでは、セキュアフラグを使用しないようにセッション Cookie を設定することもできます。

HTTP を使用するアプリケーションは中間者攻撃に対して脆弱であるため、HTTP と HTTPS を切り替えることは一般的にはお勧めできません。真に安全であるためには、ユーザーは HTTPS でサイトへのアクセスを開始し、ログアウトするまでサイトを使用し続ける必要があります。HTTP 経由でアクセスされるページから HTTPS リンクをクリックすることでさえ、潜在的に危険です。さらに説得力が必要な場合は、sslstrip [GitHub] (英語) などのツールを確認してください。

HTTP と HTTPS を切り替えていませんが、セッションが失われます。どうしたの?

セッションは、セッション Cookie を交換するか、URL に jsessionid パラメーターを追加することによって維持されます (これは、JSTL を使用して URL を出力する場合、または URL で HttpServletResponse.encodeUrl を呼び出す場合 (たとえば、リダイレクト前) に自動的に行われます。クライアントで Cookie が無効になっている場合は、jsessionid を含めるように URL を書き換えていない場合、セッションは失われます。URL 内のセッション情報が公開されないため、セキュリティ上の理由から Cookie の使用が推奨されることに注意してください。

同時セッション制御サポートを使用しようとしていますが、ログアウトして許可されたセッションを超えていないと確信している場合でも、再度ログインできません。なにが問題ですか?

web.xml ファイルにリスナーが追加されていることを確認してください。セッションが破棄されたときに Spring Security セッションレジストリに通知されるようにすることが重要です。これがないと、セッション情報はレジストリから削除されません。次の例では、web.xml ファイルにリスナーを追加します。

<listener>
		<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>

Spring Security は、create-session 属性を never に設定することにより、設定していない場合でも、どこかにセッションを作成します。なにが問題ですか?

これは通常、ユーザーのアプリケーションがどこかでセッションを作成しているが、それを認識していないことを意味します。最も一般的な原因は JSP です。多くの人は、JSP がデフォルトでセッションを作成することに気づいていません。JSP がセッションを作成しないようにするには、ページの上部に <%@ page session="false" %> ディレクティブを追加します。

セッションが作成されている場所を特定するのに問題がある場合は、場所を追跡するためのデバッグコードを追加できます。これを行う 1 つの方法は、sessionCreated メソッドで Thread.dumpStack() を呼び出す javax.servlet.http.HttpSessionListener をアプリケーションに追加することです。

POST を実行すると 403Forbidden が表示されます。なにが問題ですか?

HTTP POST では HTTP 403 Forbidden エラーが返されましたが、HTTP GET では正常に動作する場合、課題は CSRF に関連している可能性があります。CSRF トークンを提供するか、CSRF 保護を無効にします (後者は推奨されません)。

RequestDispatcher を使用してリクエストを別の URL に転送していますが、セキュリティ上の制約が適用されていません。

デフォルトでは、フィルターは転送またはインクルードに適用されません。本当にセキュリティフィルターを転送またはインクルードに適用する場合は、<filter-mapping> 要素の子要素である <dispatcher> 要素を使用して、web.xml ファイルでこれらを明示的に構成する必要があります。

Spring Security の <global-method-security> 要素をアプリケーションコンテキストに追加しましたが、Spring MVC コントローラー Bean(Struts アクションなど)にセキュリティアノテーションを追加しても、効果がないようです。なぜだめですか?

Spring Web アプリケーションでは、ディスパッチャーサーブレットの Spring MVC Bean を保持するアプリケーションコンテキストは、多くの場合、メインアプリケーションコンテキストとは別のものです。多くの場合、myapp-servlet.xml と呼ばれるファイルで定義されます。ここで、myapp は、web.xml ファイルで Spring DispatcherServlet に割り当てられた名前です。アプリケーションは複数の DispatcherServlet インスタンスを持つことができ、それぞれが独自の分離されたアプリケーションコンテキストを持ちます。これらの「子」コンテキストの Bean は、アプリケーションの他の部分には表示されません。「親」アプリケーションコンテキストは、web.xml ファイルで定義した ContextLoaderListener によってロードされ、すべての子コンテキストに表示されます。この親コンテキストは通常、<global-method-security> 要素を含むセキュリティ構成を定義する場所です。その結果、これらの Web Bean のメソッドに適用されるセキュリティ制約は強制されません。これは、Bean が DispatcherServlet コンテキストから認識できないためです。<global-method-security> 宣言を Web コンテキストに移動するか、保護する Bean をメインアプリケーションコンテキストに移動する必要があります。

一般に、個々の Web コントローラーではなく、サービス層でメソッドセキュリティを適用することをお勧めします。

確実に認証されたユーザーがいますが、一部のリクエストで SecurityContextHolder にアクセスしようとすると、認証が null になります。ユーザー情報が表示されないのはなぜですか?

ユーザー情報が表示されないのはなぜですか?

URL パターンに一致する <intercept-url> エレメントの filters='none' 属性を使用して、セキュリティフィルターチェーンからリクエストを除外した場合、そのリクエストに対して SecurityContextHolder は移入されません。デバッグログをチェックして、リクエストがフィルターチェーンを通過しているかどうかを確認します。(デバッグログを読んでいます。よね? )

承認 JSP タグは、URL 属性を使用するときに、メソッドのセキュリティアノテーションを考慮しません。なぜだめですか?

メソッドセキュリティは、<sec:authorize> で url 属性を使用するときにリンクを非表示にしません。これは、どの URL がどのコントローラーエンドポイントにマップされるかを簡単にリバースエンジニアリングできないためです。コントローラーは、ヘッダー、現在のユーザー、その他の詳細に依存して、呼び出すメソッドを決定できるため、制限があります。

Spring Security アーキテクチャに関する質問

このセクションでは、Spring Security アーキテクチャの一般的な質問について説明します。

どのパッケージクラス X が含まれているかを知るにはどうすればよいですか

クラスを見つける最良の方法は、IDE に Spring Security ソースをインストールすることです。ディストリビューションには、プロジェクトが分割された各モジュールのソース jar が含まれています。これらをプロジェクトのソースパスに追加すると、Spring Security クラス (Eclipse の Ctrl-Shift-T ) に直接移動できます。これにより、デバッグも容易になり、例外が発生したコードを直接調べてそこで何が起こっているかを確認することで、例外のトラブルシューティングを行うことができます。

名前空間要素は、従来の Bean 構成にどのようにマッピングされますか?

リファレンスガイドの名前空間の付録には、名前空間によってどの Bean が作成されるかについての概要が記載されています。blog.springsource.com (英語) には、「Spring Security 名前空間の裏側」という詳細なブログ記事もあります。詳細を知りたい場合は、コードは Spring Security 3.0 ディストリビューション内の spring-security-config モジュールにあります。おそらく、最初に標準の Spring Framework リファレンスドキュメントの名前空間解析に関する章を読む必要があります。

"ROLE_" とは何を意味し、なぜロール名にそれが必要なのですか?

Spring Security は投票者ベースのアーキテクチャを備えています。つまり、アクセスの決定は一連の AccessDecisionVoter インスタンスによって行われます。投票者は、保護されたリソース(メソッド呼び出しなど)に指定された「構成属性」に基づいて行動します。このアプローチでは、すべての属性がすべての投票者に関連するわけではありません。投票者は、属性を無視する(棄権する)時期と、属性値に基づいてアクセスを許可または拒否する時期を知る必要があります。最も一般的な投票者は RoleVoter であり、デフォルトでは、ROLE_ プレフィックスを持つ属性が見つかるたびに投票します。これは、属性(ROLE_USER など)と現在のユーザーが割り当てられている権限の名前を簡単に比較します。一致するものが見つかった場合(ROLE_USER と呼ばれる権限があります)、アクセスを許可するために投票します。それ以外の場合は、アクセスを拒否することに投票します。

RoleVoter の rolePrefix プロパティを設定することにより、プレフィックスを変更できます。アプリケーションでロールを使用するだけで、他のカスタム投票者が必要ない場合は、プレフィックスを空白の文字列に設定できます。その場合、RoleVoter はすべての属性をロールとして扱います。

Spring Security で動作するためにアプリケーションに追加する依存関係を知るにはどうすればよいですか?

それは、使用している機能と開発しているアプリケーションの種類によって異なります。Spring Security 3.0 では、プロジェクトの jar が明確に異なる機能領域に分割されているため、アプリケーションの要件からどの Spring Security jar が必要かを簡単に判断できます。すべてのアプリケーションには spring-security-core jar が必要です。Web アプリケーションを開発している場合は、spring-security-web jar が必要です。セキュリティ名前空間構成を使用している場合は、spring-security-config jar が必要です。LDAP サポートには、spring-security-ldap jar などが必要です。

サードパーティの jar の場合、状況は必ずしもそれほど明白ではありません。開始点として、事前に作成されたサンプルアプリケーションの WEB-INF/lib ディレクトリの 1 つからコピーすることをお勧めします。基本的なアプリケーションの場合は、チュートリアルサンプルから始めることができます。基本的なアプリケーションの場合は、チュートリアルサンプルから始めることができます。組み込みテストサーバーで LDAP を使用する場合は、LDAP サンプルを開始点として使用します。リファレンスマニュアルには、各 Spring Security モジュールの第 1 レベルの依存関係を一覧表示する付録も含まれており、それらがオプションであるかどうか、いつ必要になるかについての情報が含まれています。

Maven を使用してプロジェクトをビルドする場合、適切な Spring Security モジュールを依存関係として pom.xml ファイルに追加すると、フレームワークが必要とするコア jar が自動的にプルされます。Spring Security pom.xml ファイルで「オプション」とマークされているものは、必要に応じて独自の pom.xml ファイルに追加する必要があります。

組み込み ApacheDS LDAP サーバーを実行するには、どのような依存関係が必要ですか?

Maven を使用する場合は、pom.xml ファイルの依存関係に以下を追加する必要があります。

<dependency>
		<groupId>org.apache.directory.server</groupId>
		<artifactId>apacheds-core</artifactId>
		<version>1.5.5</version>
		<scope>runtime</scope>
</dependency>
<dependency>
		<groupId>org.apache.directory.server</groupId>
		<artifactId>apacheds-server-jndi</artifactId>
		<version>1.5.5</version>
		<scope>runtime</scope>
</dependency>

他の必要な jar は推移的に取り込む必要があります。

UserDetailsService とは何ですか?

UserDetailsService は、ユーザーアカウントに固有のデータをロードするための DAO インターフェースです。これには、フレームワーク内の他のコンポーネントで使用するためにそのデータをロードする以外の機能はありません。ユーザーの認証は行いません。ユーザー名とパスワードの組み合わせによるユーザーの認証は、最も一般的には DaoAuthenticationProvider によって実行されます。これは UserDetailsService とともに挿入され、ユーザーのパスワード (およびその他のデータ) をロードして送信された値と比較します。LDAP を使用する場合、このアプローチは機能しない可能性があることに注意してください。

認証プロセスをカスタマイズしたい場合は、AuthenticationProvider を自分で実装する必要があります。Spring Security 認証を Google App Engine と統合する例については、このブログ記事 (英語) を参照してください。

よくある使い方に関する質問

このセクションでは、Spring Security に関するよくある使い方の質問に対処します。

ユーザー名だけでなく、より多くの情報を使用してログインする必要があります。追加のログインフィールド (会社名など) のサポートを追加するにはどうすればよいですか ?

この質問は繰り返し出てくるため、オンラインで検索すると、より多くの情報を見つけることができます。

送信されたログイン情報は、UsernamePasswordAuthenticationFilter のインスタンスによって処理されます。追加のデータフィールドを処理するには、このクラスをカスタマイズする必要があります。1 つのオプションは、(標準の UsernamePasswordAuthenticationToken ではなく)独自にカスタマイズした認証トークンクラスを使用することです。もう 1 つのオプションは、追加のフィールドをユーザー名と連結し(たとえば、: 文字を区切り文字として使用して)、UsernamePasswordAuthenticationToken のユーザー名プロパティに渡すことです。

また、実際の認証プロセスをカスタマイズする必要があります。たとえば、カスタム認証トークンクラスを使用する場合は、それを処理するために AuthenticationProvider を作成する(または標準の DaoAuthenticationProvider を継承する)必要があります。フィールドを連結した場合は、独自の UserDetailsService を実装してフィールドを分割し、認証用の適切なユーザーデータをロードできます。

リクエストされた URL のフラグメント値のみが異なる場合(/thing1#thing2 や /thing1#thing3)? など)に異なるインターセプト URL 制約を適用するにはどうすればよいですか

フラグメントはブラウザーからサーバーに送信されないため、これを行うことはできません。サーバーの観点からは、URL は同一です。これは GWT ユーザーからのよくある質問です。

UserDetailsService でユーザーの IP アドレス(または他の Web リクエストデータ)にアクセスするにはどうすればよいですか?

インターフェースに提供される情報はユーザー名のみであるため、(スレッドローカル変数のようなものに頼らずに)できません。UserDetailsService を実装する代わりに、AuthenticationProvider を直接実装し、提供された Authentication トークンから情報を抽出する必要があります。

標準の Web セットアップでは、Authentication オブジェクトの getDetails() メソッドは WebAuthenticationDetails のインスタンスを返します。追加情報が必要な場合は、使用している認証フィルターにカスタム AuthenticationDetailsSource を挿入できます。<form-login> 要素などで名前空間を使用している場合、この要素を削除し、明示的に設定された UsernamePasswordAuthenticationFilter を指す <custom-filter> 宣言で置き換える必要があります。

UserDetailsService から HttpSession にアクセスするにはどうすればよいですか?

UserDetailsService はサーブレット API を認識していないため、できません。カスタムユーザーデータを保存する場合は、返される UserDetails オブジェクトをカスタマイズする必要があります。これは、スレッドローカル SecurityContextHolder を介していつでもアクセスできます。SecurityContextHolder.getContext().getAuthentication().getPrincipal() を呼び出すと、このカスタムオブジェクトが返されます。

本当にセッションにアクセスする必要がある場合は、Web 層をカスタマイズしてアクセスする必要があります。

UserDetailsService でユーザーのパスワードにアクセスするにはどうすればよいですか?

できません(そしてそうする方法を見つけたとしても、そうすべきではありません)。おそらくその目的を誤解しています。FAQ の前半の "UserDetailsService とは何ですか? " を参照してください。

アプリケーション内で保護された URL を動的に定義するにはどうすればよいですか?

保護された URL とセキュリティメタデータ属性の間のマッピングをアプリケーションコンテキストではなくデータベースに保存する方法についてよく尋ねられます。

最初に自問する必要があるのは、本当にこれを行う必要があるかどうかです。アプリケーションをセキュリティで保護する必要がある場合は、定義されたポリシーに基づいてセキュリティを徹底的にテストする必要もあります。本番環境に展開する前に、監査と受け入れテストが必要になる場合があります。セキュリティに敏感な組織は、構成データベースの 1 行または 2 行を変更することにより、実行時にセキュリティ設定を変更できるようにすることで、入念なテストプロセスの利点を即座に消し去ることができることを認識しておく必要があります。これを考慮に入れている場合(おそらく、アプリケーション内でセキュリティの複数のレイヤーを使用することにより)、Spring Security を使用すると、セキュリティメタデータのソースを完全にカスタマイズできます。必要に応じて、完全に動的にすることができます。

メソッドと Web セキュリティの両方が AbstractSecurityInterceptor のサブクラスによって保護されています。このサブクラスは、特定のメソッドまたはフィルター呼び出しのメタデータを取得する SecurityMetadataSource で構成されています。Web セキュリティの場合、インターセプタークラスは FilterSecurityInterceptor であり、FilterInvocationSecurityMetadataSource マーカーインターフェースを使用します。それが動作する「保護されたオブジェクト」型は FilterInvocation です。デフォルトの実装(名前空間 <http> とインターセプターを明示的に構成する場合の両方で使用)は、URL パターンのリストとそれに対応する「構成属性」(ConfigAttribute のインスタンス)のリストをメモリ内マップに格納します。

代替ソースからデータをロードするには、明示的に宣言されたセキュリティフィルターチェーン(通常は Spring Security の FilterChainProxy)を使用して FilterSecurityInterceptor Bean をカスタマイズする必要があります。名前空間は使用できません。次に、FilterInvocationSecurityMetadataSource を実装して、特定の FilterInvocation に対して必要に応じてデータをロードします。FilterInvocation オブジェクトには HttpServletRequest が含まれているため、返された属性のリストに含まれている内容に基づいて、決定の基礎となる URL またはその他の関連情報を取得できます。基本的なアウトラインは、次の例のようになります。

  • Java

  • Kotlin

	public class MyFilterSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

		public List<ConfigAttribute> getAttributes(Object object) {
			FilterInvocation fi = (FilterInvocation) object;
				String url = fi.getRequestUrl();
				String httpMethod = fi.getRequest().getMethod();
				List<ConfigAttribute> attributes = new ArrayList<ConfigAttribute>();

				// Lookup your database (or other source) using this information and populate the
				// list of attributes

				return attributes;
		}

		public Collection<ConfigAttribute> getAllConfigAttributes() {
			return null;
		}

		public boolean supports(Class<?> clazz) {
			return FilterInvocation.class.isAssignableFrom(clazz);
		}
	}
class MyFilterSecurityMetadataSource : FilterInvocationSecurityMetadataSource {
    override fun getAttributes(securedObject: Any): List<ConfigAttribute> {
        val fi = securedObject as FilterInvocation
        val url = fi.requestUrl
        val httpMethod = fi.request.method

        // Lookup your database (or other source) using this information and populate the
        // list of attributes
        return ArrayList()
    }

    override fun getAllConfigAttributes(): Collection<ConfigAttribute>? {
        return null
    }

    override fun supports(clazz: Class<*>): Boolean {
        return FilterInvocation::class.java.isAssignableFrom(clazz)
    }
}

詳細については、DefaultFilterInvocationSecurityMetadataSource のコードを参照してください。

LDAP に対して認証するが、データベースからユーザーロールをロードするにはどうすればよいですか?

LdapAuthenticationProvider Bean(Spring Security で通常の LDAP 認証を処理する)は、2 つの別個の戦略インターフェースで構成されます。1 つは認証を実行し、もう 1 つは LdapAuthenticator および LdapAuthoritiesPopulator と呼ばれるユーザー権限をロードします。DefaultLdapAuthoritiesPopulator は、LDAP ディレクトリからユーザー権限をロードし、これらを取得する方法を指定できるようにするためのさまざまな構成パラメーターを備えています。

代わりに JDBC を使用するには、スキーマに適切な SQL を使用して、インターフェースを自分で実装できます。

  • Java

  • Kotlin

public class MyAuthoritiesPopulator implements LdapAuthoritiesPopulator {
    @Autowired
    JdbcTemplate template;

    List<GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
        return template.query("select role from roles where username = ?",
                new String[] {username},
                new RowMapper<GrantedAuthority>() {
             /**
             *  We're assuming here that you're using the standard convention of using the role
             *  prefix "ROLE_" to mark attributes which are supported by Spring Security's RoleVoter.
             */
            @Override
            public GrantedAuthority mapRow(ResultSet rs, int rowNum) throws SQLException {
                return new SimpleGrantedAuthority("ROLE_" + rs.getString(1));
            }
        });
    }
}
class MyAuthoritiesPopulator : LdapAuthoritiesPopulator {
    @Autowired
    lateinit var template: JdbcTemplate

    override fun getGrantedAuthorities(userData: DirContextOperations, username: String): MutableList<GrantedAuthority?> {
        return template.query("select role from roles where username = ?",
            arrayOf(username)
        ) { rs, _ ->
            /**
             * We're assuming here that you're using the standard convention of using the role
             * prefix "ROLE_" to mark attributes which are supported by Spring Security's RoleVoter.
             */
            SimpleGrantedAuthority("ROLE_" + rs.getString(1))
        }
    }
}

次に、この型の Bean をアプリケーションコンテキストに追加し、LdapAuthenticationProvider に挿入します。これについては、リファレンスマニュアルの LDAP の章にある明示的な Spring Bean を使用した LDAP の構成に関するセクションで説明されています。この場合、構成に名前空間を使用できないことに注意してください。関連するクラスとインターフェースについては、security-api-url [Javadoc] も参照してください。

名前空間によって作成された Bean のプロパティを変更したいのですが、スキーマにはそれをサポートするものが何もありません。名前空間の使用を放棄する前に何ができますか?

名前空間の機能は意図的に制限されているため、プレーン Bean で実行できるすべての機能をカバーしているわけではありません。Bean の変更や別の依存関係の挿入など、単純なことを実行したい場合は、BeanPostProcessor を構成に追加することで実行できます。詳細については、Spring リファレンスマニュアルを参照してください。そのためには、どの Bean が作成されるかについて少し知る必要があるため、名前空間が Spring Bean にどのようにマップされるかについて、前の質問でメンションしたブログ記事も読む必要があります。

通常、必要な機能を BeanPostProcessor の postProcessBeforeInitialization メソッドに追加します。UsernamePasswordAuthenticationFilter (form-login エレメントによって作成された)によって使用される AuthenticationDetailsSource をカスタマイズするとします。リクエストから CUSTOM_HEADER という特定のヘッダーを抽出し、ユーザーの認証中にそれを使用するとします。プロセッサークラスは次のようになります。

  • Java

  • Kotlin

public class CustomBeanPostProcessor implements BeanPostProcessor {

		public Object postProcessAfterInitialization(Object bean, String name) {
				if (bean instanceof UsernamePasswordAuthenticationFilter) {
						System.out.println("********* Post-processing " + name);
						((UsernamePasswordAuthenticationFilter)bean).setAuthenticationDetailsSource(
										new AuthenticationDetailsSource() {
												public Object buildDetails(Object context) {
														return ((HttpServletRequest)context).getHeader("CUSTOM_HEADER");
												}
										});
				}
				return bean;
		}

		public Object postProcessBeforeInitialization(Object bean, String name) {
				return bean;
		}
}
class CustomBeanPostProcessor : BeanPostProcessor {
    override fun postProcessAfterInitialization(bean: Any, name: String): Any {
        if (bean is UsernamePasswordAuthenticationFilter) {
            println("********* Post-processing $name")
            bean.setAuthenticationDetailsSource(
                AuthenticationDetailsSource<HttpServletRequest, Any?> { context -> context.getHeader("CUSTOM_HEADER") })
        }
        return bean
    }

    override fun postProcessBeforeInitialization(bean: Any, name: String?): Any {
        return bean
    }
}

次に、この Bean をアプリケーションコンテキストに登録します。Spring は、アプリケーションコンテキストで定義された Bean で自動的に呼び出します。