フィルター

spring-web モジュールは、いくつかの有用なフィルターを提供します。

フォームデータ

ブラウザーは、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 ヘッダー

リクエストがロードバランサーなどのプロキシを通過すると、ホスト、ポート、スキームが変更される可能性があるため、クライアントの観点から正しいホスト、ポート、スキームを指すリンクを作成することが困難になります。

RFC 7239 [IETF] (英語) は、プロキシが元のリクエストに関する情報を提供するために使用できる Forwarded HTTP ヘッダーを定義します。

非標準ヘッダー

X-Forwarded-HostX-Forwarded-PortX-Forwarded-ProtoX-Forwarded-SslX-Forwarded-Prefix など、他の非標準ヘッダーもあります。

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/https など) をダウンストリームサーバーに通信するために使用される事実上の標準ヘッダーです。例: example.com/resource (英語)  のリクエストが localhost:8080/resource にリクエストを転送するプロキシに送信される場合、X-Forwarded-Proto: https のヘッダーを送信して、元のプロトコルが 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 パスでは、これを表示しないでください。

  • セキュリティを追加しました。例: 同一生成元ポリシー

  • アプリケーションの独立したスケーリング (異なるドメインは異なる IP アドレスを指します)

シナリオ 3: パス接頭辞を挿入する

他の場合には、プレフィックスを先頭に追加する必要がある場合があります。例: 次のプロキシからサーバーへのマッピングを考えてみましょう。

https://example.com/api/app1/{path} -> http://localhost:8080/app1/{path}

この場合、プロキシのプレフィックスは /api/app1 で、サーバーのプレフィックスは /app1 です。プロキシは X-Forwarded-Prefix: /api/app1 を送信して、元のプレフィックス /api/app1 でサーバープレフィックス /app1 をオーバーライドすることができます。

ForwardedHeaderFilter

ForwardedHeaderFilter は、a) Forwarded ヘッダーに基づいてホスト、ポート、スキームを変更し、b)これらのヘッダーを削除してさらなる影響を排除するために、リクエストを変更するサーブレットフィルターです。フィルターはリクエストをラップすることに依存しているため、RequestContextFilter など、他のフィルターよりも先に並べ替える必要があります。これは、元のリクエストではなく、変更されたリクエストで機能するはずです。

セキュリティに関する考慮事項

転送されたヘッダーには、意図したとおりにプロキシによってヘッダーが追加されたか、悪意のあるクライアントによってヘッダーが追加されたかをアプリケーションが認識できないため、セキュリティ上の考慮事項があります。これが、外部からの信頼できない Forwarded ヘッダーを削除するように、信頼の境界にあるプロキシを構成する必要がある理由です。ForwardedHeaderFilter を removeOnly=true で構成することもできます。この場合、ヘッダーは削除されますが、使用されません。

ディスパッチャーの型

非同期リクエストとエラーディスパッチをサポートするには、このフィルターを 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 フィルターのセクションを参照してください。