概要
下記の様に同じドメインで、異なるサブディレクトリを持つWebアプリケーションがある場合、それぞれに保存したCookieは互いに参照や編集が可能かを調べてみました。
siteA: http://www.example.com/
siteB: http://www.example.com/subB/
siteC: http://www.exmaple.com/subC/
CookieのPath属性について
まず、Cookieの仕様について調べてみます。FirefoxのAdd-onであるAdvanced Cookie Managerで、既に私のブラウザ上に設定されているCookie群を眺めていると、Cookieのnameやvalueの他に、Domain属性やPath属性などという属性がありました。特にPath属性が気になります。
RFCでは、RFC6265 — HTTP State Management MechanismにCookieの記載があるようですので、このRFC6265のPath属性に関する説明を読んでみます。
4.1.2.4. The Path Attribute
The scope of each cookie is limited to a set of paths, controlled by
the Path attribute. If the server omits the Path attribute, the user
agent will use the "directory" of the request-uri's path component as
the default value. (See Section 5.1.4 for more details.)The user agent will include the cookie in an HTTP request only if the
path portion of the request-uri matches (or is a subdirectory of) the
cookie's Path attribute, where the %x2F ("/") character is
interpreted as a directory separator.
(拙日本語訳ここから)--------------------------
クッキーのスコープは、Path属性に制御された一連のパスによって制限されます。サーバがPath属性を省略した場合、User Agentは、そのrequest-uriのディレクトリをデフォルト値として採用します。(詳細はSection 5.1.4)
User Agentは、request-uriのパス部分がcookieのPath属性にマッチするか、もしくは、Path属性で指定されているパスのサブディレクトリである場合にのみ、HTTPリクエストにクッキーを含めます。このとき、%x2F("/")文字は、ディレクトリセパレーターとして解釈されます。
(拙日本語訳ここまで)--------------------------
なるほど。
ということは、http://www.example.com/ (つまり、Path=/) で設定されたCookieは、http://www.example.com/subB/ (Path=/subB/)からは読めるけれども、逆にhttp://www.example.com/subB/ (Path=/subB/)で設定されたCookieは、http://www.example.com/ (つまり、Path=/)からは読めない、ということになりそうです。
検証
1. 準備
各サイトにおいて、下記のような「name=value」を保存することにします。
siteA
- example.com-main=A-1
siteB
- example.com-main=B-1
- siteB=B-2
- siteBC=B-3
siteC
- example.com-main=C-1
- siteC=C-2
- siteBC=C-3
説明
- example.com-mainは、どのサイトでも設定してみることにします。
- siteBCは、siteB, siteCの双方から設定してみることにします。
検証環境
- Firefox ESR 38.8.0
- httpd server version: Apache/2.4.16
2. ソースコードと検証結果(HTTPリクエストのCookieヘッダ)
さて、どうなりますか。
検証にあたり留意点ですが、下記ソースコードを読んで頂ければお分かりの通り、javscriptで設定していますので、1度目のURLアクセスによるGET時にはCookieヘッダは送信されません。ページ表示後に改めてCtrl+F5を押下して発生するGETリクエストを採取して、それぞれ記載しています。また、User-Agent、If-Modified-Since, If-None-Matchの各ヘッダの値は本検証には関係がありませんので省略してあります。
2-1. siteA: http://www.example.com/
<html>
<head><title>example.com-main</title></head>
<body>
<h1>example.com-main</h1>
</body>
<script type="text/javascript">
<!--
document.cookie = "example.com-main=A-1";
console.log(document.cookie);
// -->
</script>
</html>
console.log出力結果
"example.com-main=A-1"
HTTPリクエストヘッダ
GET / HTTP/1.1
Host: www.example.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: example.com-main=A-1
Connection: keep-alive
Cache-Control: max-age=0
2-2. siteB: http://www.example.com/subA
<html>
<head><title>example.com-subB</title></head>
<body>
<h1>example.com-subB</h1>
</body>
<script type="text/javascript">
<!--
document.cookie = "example.com-main=B-1";
document.cookie = "siteB=B-2";
document.cookie = "siteBC=B-3";
console.log(document.cookie);
// -->
</script>
</html>
console.log出力結果
"example.com-main=B-1; siteB=B-2; siteBC=B-3; example.com-main=A-1"
HTTPリクエストヘッダ
GET /subB/ HTTP/1.1
Host: www.example.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: example.com-main=B-1; siteB=B-2; siteBC=B-3; example.com-main=A-1
Connection: keep-alive
Cache-Control: max-age=0
2-3. siteC: http://www.example.com/subC
<html>
<head><title>example.com-subC</title></head>
<body>
<h1>example.com-subC</h1>
</body>
<script type="text/javascript">
<!--
document.cookie = "example.com-main=C-1";
document.cookie = "siteC=C-2";
document.cookie = "siteBC=C-3";
console.log(document.cookie);
// -->
</script>
</html>
console.log出力結果
"example.com-main=C-1; siteC=C-2; siteBC=C-3; example.com-main=A-1"
GET /subC/ HTTP/1.1
Host: www.example.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: example.com-main=C-1; siteC=C-2; siteBC=C-3; example.com-main=A-1
Connection: keep-alive
Cache-Control: max-age=0
2-4. 上記が終わった後、再度siteA: http://www.example.com/ へアクセス
GET / HTTP/1.1
Host: www.example.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: example.com-main=A-1
Connection: keep-alive
Cache-Control: max-age=0
3. ここまで実施した後のブラウザ上のCookie
www.example.com ドメイン配下の Cookie は下記の7つありました。
(1) example.com-main=A-1, Path=/
(2) example.com-main=B-1, Path=/subB/
(3) example.com-main=C-1, Path=/subC/
(4) siteB=B-2, Path=/subB/
(5) siteBC=B-3, Path=/subB/
(6) siteBC=C-3, Path=/subC/
(7) siteC=C-2, Path=/subC/
検証結果からまとめ
(1) Path属性を省略した場合、request-uriのサブディクレトリ部分がそのまま自動的にPath属性に設定される。(RFC6265通りの挙動)
(2) http://www.example.com/subB/ や http://www.example.com/subC/ で設定されたCookieは、上位階層の http://www.example.com/ では参照不可。
(3) http://www.example.com/ で設定されたCookieは、下位階層の http://www.example.com/subB/ や http://www.example.com/subC/ では参照可能。
(4) 上位下位階層の双方で同じnameのCookieを設定した場合、上位階層で設定されたCookieと下位階層で設定されたCookieが両方とも送信される。全く同じnameで送信される為、Cookieヘッダでは、その順序でしかこの区別が出来ない。
(Firefoxで検証した例では、Cookieヘッダの値を先頭から検索していけば、適切にそれぞれのサイトで設定されたCookieが参照出来ることになるが、RFCにこの順序は明示されていない為、ブラウザによって挙動が変わる可能性があることに留意する必要がありそう)
考察
今回の検証では、Path属性を指定しないでやってみましたが、http://www.example.com/subB/ サイトで、Path=/ として保存することも出来てしまうので、同じドメイン配下の各アプリケーションは、基本的に name が一意になるよう設計したほうが安全でしょう。