« どうしてJaveでCookieが取得できないんだろう?(3) | トップページ | どうしてJaveでCookieが取得できないんだろう?(5) »

2010年9月18日 (土)

どうしてJaveでCookieが取得できないんだろう?(4)

 前回からの続きです

前回はJavaの「expire解析失敗バグ」について触れました。これは有効期限が読み取れないためにクッキーが消滅してしまうもので、対策としてJavaのロケーションを日本からUSへ変更することをあげました。他の方法としてはJavaのバージョンを最新のものへアップデートしてください。2010年9月現在、Javaの最新版は「1.6.0_21」となっています。
自分で使う分にはアップデートだけでいいんですが、ソフトウェアの配布をする場合には利用者へ周知したり、対策する必要があるかと思います。

さてでは今回はもう一つのバグ、「クッキー適用順の怪」について触れていきます。

みなさんはJavaでログインサイトにアクセスし、ログインする際にどのようにプログラムしているでしょうか? ログインとはWebサイトからSessionIDを取得するまでのことです。

例えばmixiの場合は、http://mixi.jp/login.plというURLへ「email=abcd@example.jp&password=abcdefg&next_url=./home.pl」という文字列を「POST」します。そして帰ってきたレスポンスの中のクッキーからSessionIDを取得します。

他にはニコニコ動画の場合は、https://secure.nicovideo.jp/secure/login?site=niconicoというURLへ「mail=abcd@example.jp&password=abcdefg&next_url=」という文字列を「POST」します。そして同様にレスポンスの中のクッキーからSessionIDを取得します。

実は今回指摘するバグは、この特定のサイトのログイン時でのSessionIDの取得ができないというものです。ここで注意すべきは、セッションID取得時以外のクッキーは通常通り取得できるということです。かなり厄介ですね。

さて、よくあるログインサイトで行われる手法に、古いSessionIDを消すためにわざと有効期限の切れたCookieを送信して上書きし、新しいSessionIDをクッキーに格納するものがあります。その上書き用のクッキーはdeletedという値がよく使われます。そのあたりの詳しい事情はPHPやdeletedで検索することで知ることができます。

さて、今回Javaで問題となるのは、この古いクッキーの上書き処理です。下を見てください。

HTTP/1.1 302 Found
Date: Sat, 18 Sep 2010 11:44:27 GMT
Server: Apache
X-Powered-By: PHP/5.2.9
x-niconico-authflag: 0
Set-Cookie: user_session=deleted; expires=Fri, 18-Sep-2009 11:44:26 GMT
Set-Cookie: user_session=deleted; expires=Fri, 18-Sep-2009 11:44:26 GMT; path=/
Set-Cookie: user_session=deleted; expires=Fri, 18-Sep-2009 11:44:26 GMT; path=/; domain=.nicovideo.jp
Set-Cookie: user_session=user_session_5432109_123456789012345678; expires=Mon, 18-Oct-2010 11:44:27 GMT; path=/; domain=.nicovideo.jp
Location: http://www.nicovideo.jp/
Content-Length: 0
Connection: close
Content-Type: text/html

これは古いクッキーの上書き用のクッキーと、新しく格納するセッションIDを含んだCookieヘッダの例です。上から順に、丁寧に上書きしているのが分かります。そして最後の行に新しい(有効期限の切れていない)セッションIDが含まれているわけです。

ここで恐ろしいのは、誰もが当たり前のように上から順にクッキーが適用されていくはずだと考えることです。サイト側の人もまさにそう考え、最後にセッションIDだけが残ると考えているはずです。しかしながらここで悲劇は起こります。

以下はクッキーがCookieStoreへ格納される際に呼び出された出力です。
Java内にてクッキーが格納される順番を御覧ください。

boolean shouldAccept(URI uri, HttpCookie cookie)
uri           : https://secure.nicovideo.jp/secure/login?site=niconico
cookie name   : user_session
       value  :
user_session_5432109_123456789012345678
       domain : .nicovideo.jp
       path   : /
       maxage : 2592002
       secure : false
       port   : null

boolean shouldAccept(URI uri, HttpCookie cookie)
uri           : https://secure.nicovideo.jp/secure/login?site=niconico
cookie name   : user_session
       value  :
deleted
       domain : .nicovideo.jp
       path   : /
       maxage : -31535998
       secure : false
       port   : null

boolean shouldAccept(URI uri, HttpCookie cookie)
uri           : https://secure.nicovideo.jp/secure/login?site=niconico
cookie name   : user_session
       value  :
deleted
       domain : secure.nicovideo.jp
       path   : /
       maxage : -31535998
       secure : false
       port   : null

boolean shouldAccept(URI uri, HttpCookie cookie)
uri           : https://secure.nicovideo.jp/secure/login?site=niconico
cookie name   : user_session
       value  :
deleted
       domain : secure.nicovideo.jp
       path   : /secure/
       maxage : -31535998
       secure : false
       port   : null

 

・・・最初にセッションIDを格納し、あろうことか同ヘッダー内の上書き用クッキーにて上書きされています。セッションIDは消去され、上書き用クッキーも前述の理由で直ちに削除され、最後には何も残りません。

なぜこうなるのか? 実は複数のクッキーヘッダが存在した場合、クッキーの適用順が後尾から先頭順になっているようなのです。Java内部の実装を詳しく調べたわけではなく、今のところは明白な理由は不明です。ただHttpURLConnectionのgetHeaderFieldsによって取得できるSet-Cookieヘッダの文字列のListは既に逆順になっていました。
これはCookieManagerやCookieStoreの問題ではなく、URLConnection側の問題な気がします。

さて、このセッションIDが取得できない理屈を理解していただけたでしょうか。今回は少し長くなりましたが、私はこの問題に気づくまで1年半ぐらいかかりました。ということで「クッキー適用順の怪」の解説を終え、次回はこの問題が発生するサイトや条件をまとめ、このバグへの対処方法を検討していきます。お疲れ様でした。

次回へ続く

 

« どうしてJaveでCookieが取得できないんだろう?(3) | トップページ | どうしてJaveでCookieが取得できないんだろう?(5) »

Java」カテゴリの記事

コメント

この記事へのコメントは終了しました。

トラックバック


この記事へのトラックバック一覧です: どうしてJaveでCookieが取得できないんだろう?(4):

« どうしてJaveでCookieが取得できないんだろう?(3) | トップページ | どうしてJaveでCookieが取得できないんだろう?(5) »