yamadamn’s blog

IT関連技術で経験したこと・気になったことをたまに書きます

encodeURLとencodeRedirectURL

このエントリはJava Advent Calendar 2011の23日目となります。

Servlet APIの一部であるHttpServletResponseのencodeURLとencodeRedirectURLメソッドについて、Java EEコンテナの一つであるWebLogic Server(WLS)での扱いについて紹介します。

基本動作

まず、encodeURLやencodeRedirectURLの基本動作としては、

  • 指定したURLを、その中にセッションIDを含めてエンコードする

というだけです。例で示しましょう。

encodeURL("/test/foo?bar=baz")
⇒ /test/foo;jsessionid=4pQgTJ3LnvfTKp94272n1FfwRT2C7lsn3gxJyhVhbvL4syTn9Hdn!175414136?bar=baz

ここではjsessionidが付加されているの見て取れますが、Cookieに既にJSESSIONIDが含まれている場合はエンコードされず

encodeURL("/test/foo?bar=baz")
⇒ /test/foo?bar=baz

と引数に指定したURLがそのまま返るだけです。

上記まではencodeURLで示しましたが、encodeRedirectURLでも基本的に動作は同じです。
つまり、(昔のガラケーなど)Cookieをサポートしていないクライアントで、セッショントラッキングを実現させるために、URL中にセッションIDを含めるということになります。ここまではご存知の方も多いのではないでしょうか。

セキュリティ上の注意点

初回アクセス時はCookieが通常は存在しないため、上記APIを使うとURL中にセッションIDが含まれることになります。
URLにセッションIDが含まれてしまうと、Session HijackやSession Fixationを引き起こしやすくなるため、Cookieのみサポートする環境では、

  • url-rewriting-enabledをfalseにする

方がよいでしょうし、さらにhttpsのみ利用する環境では

  • cookie-secureをtrueにする

などの対策を行ったほうがよいでしょう。
参考: weblogic.xmlデプロイメント記述子の要素 - session-descriptor

encodeURLとencodeRedirectURLの違い

さて、上記の基本動作に挙げた例では、encodeURLとencodeRedirectURLの動作に違いはないように見えます。
では、なぜこの2つのメソッドが分かれているのでしょうか?まず、Java EEAPIドキュメントを見てみましょう。
http://doc.java.sun.com/DocWeb/#r/Java%20EE%205/javax.servlet.http.HttpServletResponse/

まずは、encodeURLメソッドからです。

encodeURL
指定された URL を、その中にセッション ID を含めてエンコードします。または、エンコードが必要ない場合は、変更されていない URL を返します。このメソッドの実装には、セッション ID を URL 内でエンコードする必要があるかどうかを判断するロジックが含まれます。たとえば、ブラウザがクッキーをサポートする場合、またはセッションの追跡がオフにされている場合は、URL のエンコードは不要です。
セッション追跡を確実に行うには、サーブレットにより発行されたすべての URL が、このメソッドを通じて処理される必要があります。そうしないと、クッキーをサポートしないブラウザでは、URL の書き換えによるセッション管理を使用することはできません。

基本動作の項で説明した内容が十二分に記載されていますね。さすがAPIドキュメントです。
次に、encodeRedirectURLメソッドです。

encodeRedirectURL
sendRedirect メソッド内で使用するために、指定された URL をエンコードします。または、エンコーディングが必要ない場合は、変更しないで URL を返します。このメソッドの実装には、セッション ID を URL にエンコードする必要があるかどうかを判断するロジックが含まれています。この判断を行う規則が、通常のリンクをエンコードするかどうかの判断で使用する規則と異なる可能性があるため、このメソッドは encodeURL メソッドと分離されています。
HttpServletResponse.sendRedirect メソッドへ送られるすべての URL は、このメソッドを通じて処理される必要があります。そうしないと、クッキーをサポートしないブラウザで、URL の書き換えによるセッション管理を使用することはできません。

これを確認する限り、

  • sendRedirectメソッド内で使用するためだけに分かれている
  • コンテナ実装によって違いがありそうだが、詳細は分からない

となります。それでは、WLSの場合はどのようになっているのでしょう。
WLSは残念ながらオープンソース製品ではないため、基本的にはドキュメントから仕様を確認することになりますが、検索すると以下がヒットします。

プラグイン(Apache、NSAPI、ISAPI、HttpClusterServlet、またはHttpProxyServlet)を使用しており、バックエンド・サーバーでURL書換え(response.sendRedirect(url)またはresponse.encodeRedirectURL(url))を使用している場合は、一定の条件を満たすと、URLにPathTrimパラメータとPathPrependパラメータの両方が適用されます。一定の条件とは、「PathPrependがnullであるか、PathPrependがすでに適用されている」というもので、PathTrimはこのいずれかを満たす場合にのみ適用されます。

セッションとセッション永続性の使用 - URL書換えのコーディングに関するガイドライン

…よく、分からない日本語ですね*1
ただ、Webサーバプラグインの PathTrim/PathPrepend パラメータに関係しそうということは何となく分かります。

Webサーバプラグインとは

別名プロキシプラグインとも呼ばれ、Webサーバに導入することで、WLSとの連係を行うためのものです。

また、PathTrim/PathPrepend パラメータはそれぞれ以下のような役割を持っています。

PathTrim
特定のパスを先頭から削除してWLSに転送する
PathPrepend
特定のパスを先頭に追加してWLSに転送する
パラメータ設定例 Webサーバ上のURI WLS上のURI
PathTrim=/weblogic /weblogic/test/foo /test/foo
PathPrepend=/test /foo /test/foo
PathTrim=/weblogic,PathPrepend=/test /weblogic/foo /test/foo

のようになり、WebサーバとWLSのパス階層が何らかの理由により一致していない場合に、これを補正することができます。
参考: Webサーバー・プラグインの一般的なパラメータ
勘のよい方は、この辺で気づかれたかもしれませんが、実際にWLSでの動作を検証してみましょう。
以下の条件でテストしてみることにします。

  • コンテキストパス=/test
  • メソッドに渡す引数=/test/foo
パラメータ設定例 encodeURL encodeRedirectURL
PathTrim=/weblogic /test/foo /weblogic/test/foo
PathPrepend=/test /test/foo /foo
PathTrim=/weblogic,PathPrepend=/test /test/foo /weblogic/foo

つまり、

  • encodeURLの結果は「/test/foo」のまま変わっていない
  • encodeRedirectURLの結果は、PathTrimやPathPrependの値に応じて変わる

ことになります。
言い換えると、encodeRedirectURLでラップしたURIをsendRedirectメソッドに渡すことで、WebサーバでPathTrimやPathPrependが設定されていても、クライアントに正しいリダイレクトレスポンス*2を返すことができるようになります。

サマリ

WLSのencodeRedirectURLは以下の条件を満たす場合、 リダイレクト用のURLを生成するために特殊な動作を行います。

  1. Webサーバプラグインを利用しており、PathTrimやPathPrependパラメータを使っている
  2. メソッドに渡す引数がコンテキストパスから始まっている*3

結論としては

  • sendRedirectメソッドに渡すための引数は、encodeRedirectURLでラップするようにしましょう!
  • APIドキュメントはよく読んでおきましょう。

というお話でした。誰得なマニアックなネタですみません。。

*1:英語ドキュメントも同様なので、元々の記述がよくないと思います

*2:実際には302ステータス+WLSによって絶対URLに変換されたLocationヘッダとして返される

*3:この条件は説明しませんでしたが、別のコンテキストパスや絶対URLにリダイレクトしたい場合もあるため、動作としては納得できるものです