フィルター
サーブレット API では、フィルターの残りの処理 チェーンとターゲット Servlet の前後にインターセプションスタイルのロジックを適用するために jakarta.servlet.Filter を追加できます。
spring-web モジュールには、いくつかの組み込み Filter 実装があります。
Spring アプリケーションで使用するための基本クラスの実装もあります。
GenericFilterBean— Spring Bean として構成されたFilterの基本クラス。SpringApplicationContextライフサイクルと統合されます。OncePerRequestFilter—GenericFilterBeanの拡張であり、リクエストの開始時、つまりREQUESTディスパッチフェーズで単一の呼び出しをサポートし、FORWARDディスパッチによる以降の処理を無視します。このフィルターは、FilterがASYNCおよびERRORディスパッチに関与するかどうかも制御します。
サーブレットフィルターは、web.xml またはサーブレットアノテーションを使用して設定できます。Spring Boot アプリケーションでは、フィルターを Bean として宣言すると、Boot が設定します。
フォームデータ
ブラウザーは、HTTP GET または HTTP POST を介してのみフォームデータを送信できますが、ブラウザー以外のクライアントも HTTP PUT、PATCH、DELETE を使用できます。サーブレット API では、HTTP POST のフォームフィールドアクセスのみをサポートするために ServletRequest.getParameter*() メソッドが必要です。
spring-web モジュールは FormContentFilter を提供し、コンテンツ型 application/x-www-form-urlencoded の HTTP PUT、PATCH、DELETE リクエストをインターセプトし、リクエストの本文からフォームデータを読み取り、ServletRequest をラップして、ServletRequest.getParameter*() ファミリーのメソッドでフォームデータを利用できるようにします。
Forwarded ヘッダー
リクエストがチェーンのプロキシを経由するにつれて、スキーム、ホスト、ポート、リモートアドレス、ローカルアドレスなどのリクエストの詳細が変更されます。プロキシはホップを追跡するヘッダーを挿入でき、これにより元のクライアントの視点からリクエストを復元できます。これにより、アプリケーションは外部クライアント向けに自己参照リンクを作成できます。
プロキシが使用できるヘッダーには、2 つの選択肢があります。
RFC 7239 [IETF] (英語) は、プロキシされたリクエストの チェーンの各コンポーネントに対して個別の属性を持つ単一のヘッダーである
"Forwarded"HTTP ヘッダーを、次の構文で定義します。"X-Forwarded-"プレフィックス付きヘッダーは、標準規格よりも前に開発された従来の方式であり、リクエストコンポーネントごとに個別のヘッダーを使用します。
Spring Framework は両方の方式をサポートしています。現在、ほとんどのプロキシは事実上の標準として、オリジナルの "X-Forwarded" ヘッダーのみをサポートしています。
セキュリティを最大限に確保するためには、信頼関係の境界にあるプロキシは、使用されるヘッダーの種類に関わらず、標準の "Forwarded" ヘッダーと "X-Forwarded-" ヘッダーの両方をリセットするように構成する必要があります。同様に、Spring で転送ヘッダーの処理を構成する際には、使用するヘッダーの種類を指定する必要があります。セキュリティに関する考慮事項については、このセクションの後半で詳しく説明します。 |
X 転送ヘッダー
このセクションでは、サポートされている "X-Forwarded" ヘッダーについて説明します。
X 転送ホスト
X-Forwarded-Host: <host> [Mozilla] は標準ではありませんが、元のホストをダウンストリームサーバーに通信するために使用される事実上の標準ヘッダーです。例: example.com/resource (英語) のリクエストが localhost:8080/resource にリクエストを転送するプロキシに送信された場合、X-Forwarded-Host: example.com のヘッダーを送信して、元のホストが example.com であったことをサーバーに通知できます。
X 転送ポート
X-Forwarded-Port: <port> は標準ではありませんが、元のポートをダウンストリームサーバーに通信するために使用される事実上の標準ヘッダーです。例: example.com/resource (英語) のリクエストが localhost:8080/resource にリクエストを転送するプロキシに送信される場合、X-Forwarded-Port: 443 のヘッダーを送信して、元のポートが 443 であったことをサーバーに通知できます。
X-Forwarded-Proto
X-Forwarded-Proto: (https|http) [Mozilla] は標準ではありませんが、元のプロトコル (https/http など) を下流サーバーに通信するために使用される事実上の標準ヘッダーです。例: example.com/resource (英語) のリクエストがプロキシに送信され、プロキシがリクエストを localhost:8080/resource に転送する場合、元のプロトコルが https であったことをサーバーに通知するために X-Forwarded-Proto: https のヘッダーを送信できます。
X-Forwarded-SSL
X-Forwarded-Ssl: (on|off) は標準ではありませんが、元のプロトコル (https/https など) を下流のサーバーに通信するために使用される事実上の標準ヘッダーです。例: example.com/resource (英語) のリクエストがプロキシに送信され、プロキシがリクエストを localhost:8080/resource に転送する場合、元のプロトコルが https であったことをサーバーに通知する X-Forwarded-Ssl: on のヘッダーが使用されます。
X 転送プレフィックス
X-Forwarded-Prefix: <prefix> (英語) は標準ではありませんが、元の URL パスプレフィックスをダウンストリームサーバーに通信するために使用される事実上の標準ヘッダーです。
X-Forwarded-Prefix の使用は デプロイシナリオによって異なる場合があり、ターゲットサーバーのパスプレフィックスを置換、削除、先頭に追加できるように柔軟にする必要があります。
シナリオ 1: パスプレフィックスを上書きする
https://example.com/api/{path} -> http://localhost:8080/app1/{path} プレフィックスは、キャプチャーグループ {path} の前のパスの開始位置です。プロキシの場合、プレフィックスは /api ですが、サーバーの場合、プレフィックスは /app1 です。この場合、プロキシは X-Forwarded-Prefix: /api を送信して、元のプレフィックス /api でサーバープレフィックス /app1 をオーバーライドすることができます。
シナリオ 2: パスプレフィックスを削除する
場合によっては、アプリケーションでプレフィックスを削除したい場合があります。例: 次のプロキシからサーバーへのマッピングを考えてみましょう。
https://app1.example.com/{path} -> http://localhost:8080/app1/{path}
https://app2.example.com/{path} -> http://localhost:8080/app2/{path} プロキシにはプレフィックスがありませんが、アプリケーション app1 と app2 にはそれぞれパスプレフィックス /app1 と /app2 があります。プロキシは X-Forwarded-Prefix: を送信して、空のプレフィックスでサーバープレフィックス /app1 および /app2 をオーバーライドすることができます。
この デプロイシナリオの一般的なケースは、ライセンスが運用アプリケーションサーバーごとに支払われる場合であり、料金を削減するにはサーバーごとに複数のアプリケーションをデプロイすることが望ましいです。もう 1 つの理由は、サーバーの実行に必要なリソースを共有するために、同じサーバー上でより多くのアプリケーションを実行することです。 これらのシナリオでは、同じサーバー上に複数のアプリケーションが存在するため、アプリケーションには空ではないコンテキストルートが必要です。ただし、アプリケーションが次のような利点を提供するさまざまなサブドメインを使用する可能性があるパブリック API の URL パスでは、これを表示しないでください。
|
シナリオ 3: パス接頭辞を挿入する
他の場合には、プレフィックスを先頭に追加する必要がある場合があります。例: 次のプロキシからサーバーへのマッピングを考えてみましょう。
https://example.com/api/app1/{path} -> http://localhost:8080/app1/{path} この場合、プロキシのプレフィックスは /api/app1 で、サーバーのプレフィックスは /app1 です。プロキシは X-Forwarded-Prefix: /api/app1 を送信して、元のプレフィックス /api/app1 でサーバープレフィックス /app1 をオーバーライドすることができます。
X-Forwarded-For
X-Forwarded-For: <address> [Mozilla] は、クライアントの元の InetSocketAddress を下流サーバーに伝達するために使用される事実上の標準ヘッダーです。例: リクエストが [fd00:fefe:1::4] のクライアントから 192.168.0.1 のプロキシに送信された場合、HTTP リクエストに含まれる「リモートアドレス」情報は、プロキシではなくクライアントの実際のアドレスを反映します。
ForwardedHeaderFilter
ForwardedHeaderFilter は、標準の "Forwarded" または "X-Forwarded" ヘッダーの情報に一致するようにリクエストを変更し、さらにそれらのヘッダーを削除して影響を排除するサーブレットフィルターです。このフィルターはリクエストをラップするため、すべてのダウンストリームハンドラーが変更後のリクエストを認識できるように、RequestContextFilter などの他のフィルターよりも前に配置する必要があります。
セキュリティに関する考慮事項
転送ヘッダーは、信頼できるプロキシによって設定されることを想定しており、外部からは決して許可されません。信頼関係の境界に位置するプロキシは、標準の "Forwarded" ヘッダーと "X-Forwarded" ヘッダーの両方を含む転送ヘッダーを、どちらを使用しているかに関わらず削除する必要があります。これは、両方のヘッダーをチェックする可能性のあるアプリケーションを保護するためです。
ForwardedHeaderFilter を作成する際に、標準の "Forwarded" ヘッダーまたは "X-Forwarded" ヘッダーのどちらを使用するかを指定できます。フィルターの別のプロパティを使用すると、"X-Forwarded-Prefix" の使用をオン / オフに切り替えることができます。
ForwardedHeaderFilter は removeOnly モードで設定することができ、その場合、転送されたヘッダーは使用せずにリクエストから削除されます。
ディスパッチャーの型
非同期リクエストとエラーディスパッチをサポートするには、このフィルターを DispatcherType.ASYNC および DispatcherType.ERROR にマップする必要があります。Spring Framework の AbstractAnnotationConfigDispatcherServletInitializer ( サーブレット構成を参照) を使用する場合、すべてのディスパッチ型に対してすべてのフィルターが自動的に登録されます。ただし、web.xml 経由でフィルターを登録する場合、または FilterRegistrationBean 経由で Spring Boot にフィルターを登録する場合は、DispatcherType.REQUEST に加えて DispatcherType.ASYNC および DispatcherType.ERROR を必ず含めてください。
浅い ETag
ShallowEtagHeaderFilter フィルターは、レスポンスに書き込まれたコンテンツをキャッシュし、そこから MD5 ハッシュを計算することにより、「浅い」ETag を作成します。次回クライアントが送信するとき、同じことを行いますが、計算された値を If-None-Match リクエストヘッダーと比較し、2 つが等しい場合、304(NOT_MODIFIED)を返します。
この戦略では、リクエストごとに完全なレスポンスを計算する必要があるため、ネットワーク帯域幅は節約されますが、CPU は節約されません。状態を変更する HTTP メソッドと、If-Match や If-Unmodified-Since などのその他の HTTP 条件付きリクエストヘッダーは、このフィルターの範囲外です。コントローラーレベルの他の戦略では、計算を回避し、HTTP 条件付きリクエストをより広範にサポートできます。HTTP キャッシングを参照してください。
このフィルターには、次のような弱い ETag を書き込むようにフィルターを構成する writeWeakETag パラメーターがあります: W/"02a2d595e6ed9a0b24f027f2b63b134d6" (RFC 7232 セクション 2.3 [IETF] (英語) で定義)。
非同期リクエストをサポートするには、フィルターが最後の非同期ディスパッチの終わりまで ETag を遅延させて正常に生成できるように、このフィルターを DispatcherType.ASYNC にマップする必要があります。Spring Framework の AbstractAnnotationConfigDispatcherServletInitializer ( サーブレット構成を参照) を使用する場合、すべてのディスパッチ型に対してすべてのフィルターが自動的に登録されます。ただし、web.xml 経由でフィルターを登録する場合、または FilterRegistrationBean 経由で Spring Boot にフィルターを登録する場合は、必ず DispatcherType.ASYNC を含めてください。
CORS
Spring MVC は、コントローラーのアノテーションを介して CORS 構成をきめ細かくサポートします。ただし、Spring Security で使用する場合は、Spring Security の チェーンフィルターよりも先に並べ替える必要がある組み込みの CorsFilter を使用することをお勧めします。
詳細については、CORS および CORS フィルターのセクションを参照してください。
URL ハンドラー
コントローラーのエンドポイントを、URL パスの末尾のスラッシュの有無にかかわらずルートと一致させたい場合があります。例: "GET/home" と "GET/home/" はどちらも、@GetMapping("/home") アノテーションが付けられたコントローラーメソッドによって処理される必要があります。
Spring は、URL パスから末尾のスラッシュを削除する UrlHandlerFilter を提供します。これにより、末尾のスラッシュの有無にかかわらず、パスの一貫性が確保されます。これは、URL ベースの認可判定と Web フレームワークのリクエストマッピングの不一致を回避するために重要です。このフィルターは、以下のいずれかの方法で末尾のスラッシュを削除できます。
末尾のスラッシュなしでクライアントを同じパスに送信する HTTP リダイレクトステータスで応答します。
リクエストをラップして、末尾のスラッシュを削除します。
歴史的に、Spring MVC は URL パスの末尾のスラッシュマッチングをサポートしていました。この機能はセキュリティ上の理由から 6.0 で非推奨となり、7.0 では削除されました。UrlHandlerFilter はより安全な代替手段を提供しています。 |
ブログアプリケーション用に UrlHandlerFilter をインスタンス化して構成する方法は次のとおりです。
Java
Kotlin
UrlHandlerFilter urlHandlerFilter = UrlHandlerFilter
// will HTTP 308 redirect "/blog/my-blog-post/" -> "/blog/my-blog-post"
.trailingSlashHandler("/blog/**").redirect(HttpStatus.PERMANENT_REDIRECT)
// will wrap the request to "/admin/user/account/" and make it as "/admin/user/account"
.trailingSlashHandler("/admin/**").wrapRequest()
.build();val urlHandlerFilter = UrlHandlerFilter
// will HTTP 308 redirect "/blog/my-blog-post/" -> "/blog/my-blog-post"
.trailingSlashHandler("/blog/**").redirect(HttpStatus.PERMANENT_REDIRECT)
// will wrap the request to "/admin/user/account/" and make it as "/admin/user/account"
.trailingSlashHandler("/admin/**").wrapRequest()
.build()次の点に留意してください。
ルートパス
"/"は末尾のスラッシュの処理から除外されます。@RequestMapping("/")は、型レベルのマッピングに末尾のスラッシュを追加するため、末尾のスラッシュの処理が適用される場合はマッピングされません。代わりに、@RequestMapping(パス属性なし) を使用してください。