1. ホーム
  2. spring

[解決済み] Springセキュリティフィルタチェーンの仕組み

2022-04-20 08:09:58

質問

Springのセキュリティは、リクエストをインターセプトし、(認証がないことを)検出し、認証エントリポイントにリダイレクトするか、認可サービスにリクエストを渡し、最終的にリクエストがサーブレットにヒットするか、セキュリティ例外(未認証または未認証)を投げる、フィルタのチェーンで構築されていることを私は理解しています。 デレゲートフィットラープロキシー は、これらのフィルタをまとめています。タスクを実行するために、これらのフィルタは以下のようなサービスにアクセスします。 ユーザー詳細サービス 認証管理(AuthenticationManager .

チェーン内のキーフィルターは以下の通りです(順番に)。

  • SecurityContextPersistenceFilter (JSESSIONIDからAuthenticationをリストアする)
  • UsernamePasswordAuthenticationFilter (認証を行う)
  • ExceptionTranslationFilter (FilterSecurityInterceptor からのセキュリティ例外をキャッチ)
  • FilterSecurityInterceptor (認証や認可の例外を投げることがある)

これらのフィルターがどのように使用されるのか、私は混乱しています。春に提供されたフォームログインのためのものでしょうか。 UsernamePasswordAuthenticationFilter にのみ使用されます。 /ログイン 後者のフィルターはないのですか?また フォームログイン 名前空間要素がこれらのフィルタを自動構成しているか?すべてのリクエストは (認証されたかどうかにかかわらず) FilterSecurityInterceptor をログインしていないurlで使用できますか?

REST API のセキュリティを確保するために JWTトークン ログイン時に取得される 私は2つの名前空間の設定を行う必要があります http タグは、権利?1つは /login UsernamePasswordAuthenticationFilter また、REST url 用には、カスタムの JwtAuthenticationFilter .

を2つ設定することは http 要素で、2つの springSecurityFitlerChains ? は UsernamePasswordAuthenticationFilter を宣言するまでは、デフォルトでオフになっています。 form-login ? をどのように置き換えるのですか? SecurityContextPersistenceFilter を取得するフィルタで Authentication を既存の JWT-token よりも JSESSIONID ?

解決方法は?

Springのセキュリティフィルタチェーンは、非常に複雑で柔軟なエンジンです。

<ブロッククオート

チェーン内の主要なフィルタは以下の通りです(順番に)。

  • SecurityContextPersistenceFilter (JSESSIONIDからAuthenticationをリストアする)
  • UsernamePasswordAuthenticationFilter (認証の実行)
  • ExceptionTranslationFilter (FilterSecurityInterceptor からのセキュリティ例外をキャッチ)
  • FilterSecurityInterceptor (認証や認可の例外を投げることがある)

を見てみると 現在の安定版リリース 4.2.1 のドキュメント セクション 13.3 フィルタの順序付け で、フィルターチェーン全体のフィルター構成を見ることができます。

13.3 フィルタの順序付け

チェーン内で定義されるフィルターの順番は非常に重要です。 実際に使用するフィルターに関係なく、その順番は次のとおりです。 は次のようになります。

  1. チャネルプロセシングフィルタ 別のプロトコルにリダイレクトする必要があるためです。

  2. SecurityContextPersistenceFilter そのため、Web リクエストの最初に SecurityContext を SecurityContextHolder にセットアップし SecurityContextへの変更は、HttpSessionにコピーすることができます。 ウェブリクエストが終了したとき(次のウェブリクエストで使用できる状態)。

  3. ConcurrentSessionFilter なぜなら、SecurityContextHolder 機能を使用しており、 プリンシパルからの継続的なリクエストを反映するために SessionRegistry を更新する必要があるからです。

  4. 認証処理メカニズム - - (英語 ユーザー名・パスワード・認証フィルター , カサ認証フィルター , BasicAuthenticationFilter などのように、SecurityContextHolderが 有効な認証要求トークンを含むように変更されます。

  5. SecurityContextHolderAwareRequestFilter もし、Spring Securityを意識したHttpServletRequestWrapperをインストールするために使用するのであれば サーブレットコンテナ

  6. JaasApiIntegrationFilter がある場合は JaasAuthenticationToken がSecurityContextHolderにある場合、これはFilterChainを JaasAuthenticationToken に含まれるサブジェクト

  7. RememberMeAuthenticationFilter そのため、以前の認証処理メカニズムがSecurityContextHolderを更新しなかった場合、SecurityContextHolderが更新されます。 と、リクエストに応じたクッキーが提示され、リメンバーサービスが可能になる。 を実行すると、適切な記憶されたAuthenticationオブジェクトが置かれます。 そこ

  8. 匿名認証フィルター そのため、それ以前の認証処理メカニズムがSecurityContextHolderを更新しなかった場合、SecurityContextHolderを更新します。 匿名のAuthenticationオブジェクトが置かれます。

  9. ExceptionTranslationFilter は、Spring Securityの例外をキャッチして、HTTPエラーレスポンスを返すか、あるいは 適切なAuthenticationEntryPointを起動することができます。

  10. FilterSecurityInterceptor Web URI を保護し、アクセスが拒否されたときに例外を発生させるためのものです。

それでは、ひとつひとつ質問を進めていきたいと思います。

<ブロッククオート

このフィルターがどのように使われているのか、よくわかりません。つまり、春の 提供されたフォームログインでは、UsernamePasswordAuthenticationFilterのみが使用されます。 は /login 用で、後者のフィルタはないのでしょうか?form-login 名前空間は 要素がこれらのフィルタを自動構成しているか?すべてのリクエストは (認証されたかどうかにかかわらず)非ログイン用のFilterSecurityInterceptorに到達します。 のURLを教えてください。

を設定した後は <security-http> セクションのそれぞれについて、少なくとも1つの認証メカニズムを提供する必要があります。これは、先ほど参照したSpring Securityのドキュメントにある「13.3 Filter Ordering」のセクションのグループ4にマッチするフィルタのうちのひとつでなければなりません。

これは、設定可能な最小限の有効な security:http 要素です。

<security:http authentication-manager-ref="mainAuthenticationManager" 
               entry-point-ref="serviceAccessDeniedHandler">
    <security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
</security:http>

するだけで、これらのフィルターはフィルターチェーンプロキシーで設定されます。

{
        "1": "org.springframework.security.web.context.SecurityContextPersistenceFilter",
        "2": "org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter",
        "3": "org.springframework.security.web.header.HeaderWriterFilter",
        "4": "org.springframework.security.web.csrf.CsrfFilter",
        "5": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter",
        "6": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter",
        "7": "org.springframework.security.web.authentication.AnonymousAuthenticationFilter",
        "8": "org.springframework.security.web.session.SessionManagementFilter",
        "9": "org.springframework.security.web.access.ExceptionTranslationFilter",
        "10": "org.springframework.security.web.access.intercept.FilterSecurityInterceptor"
    }

注:私は、FilterChainProxyを@Autowiresしてその内容を返す単純なRestControllerを作ることでそれらを得ている。

    @Autowired
    private FilterChainProxy filterChainProxy;

    @Override
    @RequestMapping("/filterChain")
    public @ResponseBody Map<Integer, Map<Integer, String>> getSecurityFilterChainProxy(){
        return this.getSecurityFilterChainProxy();
    }

    public Map<Integer, Map<Integer, String>> getSecurityFilterChainProxy(){
        Map<Integer, Map<Integer, String>> filterChains= new HashMap<Integer, Map<Integer, String>>();
        int i = 1;
        for(SecurityFilterChain secfc :  this.filterChainProxy.getFilterChains()){
            //filters.put(i++, secfc.getClass().getName());
            Map<Integer, String> filters = new HashMap<Integer, String>();
            int j = 1;
            for(Filter filter : secfc.getFilters()){
                filters.put(j++, filter.getClass().getName());
            }
            filterChains.put(i++, filters);
        }
        return filterChains;
    }

ここでは、単に <security:http> 要素に最小限の設定をするだけで、すべてのデフォルトフィルターが含まれますが、どれも Authentication タイプではありません (13.3 フィルターの順序セクションの 4 番目のグループ)。ということは、実際には security:http 要素では、SecurityContextPersistenceFilter、ExceptionTranslationFilter、FilterSecurityInterceptorが自動設定されます。

実際には、1つの認証処理機構を設定する必要があり、セキュリティネームスペースBeanの処理でも起動時にエラーを投げて、そのためのクレームが発生しますが、それを回避することができます。 <http:security>

もし私が基本的な <form-login> をコンフィギュレーションに追加すると、このようになります。

<security:http authentication-manager-ref="mainAuthenticationManager">
    <security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
    <security:form-login />
</security:http>

さて、filterChainはこのようになります。

{
        "1": "org.springframework.security.web.context.SecurityContextPersistenceFilter",
        "2": "org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter",
        "3": "org.springframework.security.web.header.HeaderWriterFilter",
        "4": "org.springframework.security.web.csrf.CsrfFilter",
        "5": "org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter",
        "6": "org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter",
        "7": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter",
        "8": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter",
        "9": "org.springframework.security.web.authentication.AnonymousAuthenticationFilter",
        "10": "org.springframework.security.web.session.SessionManagementFilter",
        "11": "org.springframework.security.web.access.ExceptionTranslationFilter",
        "12": "org.springframework.security.web.access.intercept.FilterSecurityInterceptor"
    }

さて、この2つのフィルタ org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter と org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter が作成され、 FilterChainProxy に設定されます。

では、質問です。

<ブロッククオート

springが提供するform-loginの場合ということでしょうか。 UsernamePasswordAuthenticationFilterは/loginにのみ使用され、/loginには使用されません。 後者のフィルタがない?

はい、リクエストがUsernamePasswordAuthenticationFilterのurlにマッチした場合、ログイン処理メカニズムを完了させようとするために使用されます。このURLは設定することができ、すべてのリクエストにマッチするように動作を変更することもできます。

また、同じFilterchainProxyに複数の認証処理機構(HttpBasic、CASなど)を設定することも可能です。

form-login 名前空間要素はこれらのフィルタを自動設定するのでしょうか?

いいえ、form-login要素はUsernamePasswordAUthenticationFilterを設定し、あなたがログインページのURLを提供しない場合、それはまた、単純な自動生成ログインページで終わるorg.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilterを設定します。

その他のフィルターは、デフォルトでは <security:http> 要素で security:"none" 属性があります。

すべてのリクエスト(認証済みかどうかにかかわらず)は、非ログインURLのFilterSecurityInterceptorに到達するのでしょうか?

これは、リクエストが要求されたURLに到達する権利を持っているかどうかを管理する要素であるため、すべてのリクエストが到達するはずです。しかし、前に処理されたフィルタのいくつかは、単に FilterChain.doFilter(request, response); . 例えば、CSRFフィルターは、リクエストにcsrfパラメータがない場合、フィルターチェーンの処理を停止する可能性があります。

<ブロッククオート

ログイン時に取得するJWT-tokenでREST APIを保護したい場合、どうすればよいでしょうか。私は2つの名前空間設定httpタグ、権利を設定する必要がありますか?もう一つは、/loginに UsernamePasswordAuthenticationFilter と、REST url 用のカスタム JwtAuthenticationFilter .

いいえ、この方法を強制されるわけではありません。両方の宣言が可能です。 UsernamePasswordAuthenticationFilterJwtAuthenticationFilter を同じ http 要素に含めることができますが、これはそれぞれのフィルタの具体的な動作に依存します。どちらのアプローチも可能であり、最終的にどちらを選択するかは自分の好みによります。

<ブロッククオート

2つのhttp要素を設定すると、2つのspringSecurityFitlerChainが作成されるのでしょうか?

はい、その通りです。

<ブロッククオート

フォームログインを宣言するまでは、UsernamePasswordAuthenticationFilterはデフォルトでオフになっているのでしょうか?

はい、私が投稿した各設定で発生したフィルタで確認できます。

SecurityContextPersistenceFilterを、JSESSIONIDではなく、既存のJWT-tokenから認証を取得するものに置き換えるにはどうすればよいですか?

SecurityContextPersistenceFilterを使用しないようにするには、次のように設定します。 セッション戦略 <http:element> . このように設定するだけです。

<security:http create-session="stateless" >

あるいは、この場合、別のフィルターで上書きすることができます。 <security:http> 要素を使用します。

<security:http ...>  
   <security:custom-filter ref="myCustomFilter" position="SECURITY_CONTEXT_FILTER"/>    
</security:http>
<beans:bean id="myCustomFilter" class="com.xyz.myFilter" />

EDIT

<ブロッククオート

1つのFilterchainProxyに複数の認証処理機構を設定することも可能です。複数の(Spring実装の)認証フィルタを宣言した場合、後者が最初の認証で行った認証を上書きするのでしょうか?これは、複数の認証プロバイダを持つこととどう関係しているのでしょうか?

これは最終的には各フィルタの実装に依存しますが、少なくとも後者の認証フィルタは、先行するフィルタによって行われた認証を上書きすることができるのは事実です。

しかし、必ずしもそうなるとは限りません。私は、セキュリティで保護されたRESTサービスにおいて、Httpヘッダーまたはリクエストボディの内部で提供される一種の認証トークンを使用する実例をいくつか持っています。そこで、私はそのトークンを回収する2つのフィルタを設定します。1つはHttpヘッダから、もう1つは自身のRESTリクエストのリクエストボディから回収します。もしある http リクエストが認証トークンを Http ヘッダとリクエストボディの両方で提供した場合、両方のフィルタが認証メカニズムを実行しようとし、それをマネージャに委任するという事実は事実ですが、これは簡単に回避することができ、単にリクエストがすでに認証されているかどうかを doFilter() メソッドを使用します。

複数の認証フィルターを持つことは、複数の認証プロバイダーを持つことに関連しますが、それを強制してはいけません。なぜなら、どちらのフィルタも同じタイプの Authentication オブジェクトを生成するので、どちらの場合も認証マネージャはそれを同じプロバイダに委譲するからです。

これとは逆に、私も1つのUsernamePasswordAuthenticationFilterを公開するシナリオを持っていますが、ユーザー資格情報は両方ともDBまたはLDAPに含まれることができます。

つまり、認証フィルタの量が認証プロバイダの量を決めるのでもなく、プロバイダの量がフィルタの量を決めるのでもない、ということがよくわかると思います。

<ブロッククオート

また、ドキュメントによると、SecurityContextPersistenceFilterはSecurityContextのクリーニングを担当しており、これはスレッドプーリングのために重要である。もし、これを省略したり、カスタムで実装した場合、クリーニングを手動で実装しなければならないのですよね?チェーンをカスタマイズするときに、より多くの似たような混乱がありますか?

このフィルタについては、以前はよく見ていなかったのですが、前回の質問を受けて実装を確認したところ、Springの常として、ほぼ全ての設定、拡張、上書きが可能でした。

その SecurityContextPersistenceFilter のデリゲートです。 SecurityContextRepository(セキュリティコンテキストレポジトリ を実装することで、SecurityContext の検索を行うことができます。デフォルトでは HttpSessionSecurityContextRepository (セキュリティコンテキストレポジトリ) が使われますが、これはフィルタのコンストラクタで変更することができます。したがって、ゼロからすべてを作り始めるよりも、あなたのニーズに合ったSecurityContextRepositoryを書き、SecurityContextPersistenceFilterでそれを設定し、その実績ある動作を信頼する方がよいかもしれない。