- HTTP Cookieは、サーバーがユーザーのWebブラウザに送信するデータの一部です。
- ブラウザは、このデータの一部を保存することができ、次の要求時に送信することができ、訪問者の状態を保存する目的で使用します。
Cookieの目的
1.セッション管理(ログイン)
- Cookieは、ウェブサイトのショッピングカート機能のために導入されました。[1] [2] (最近ではサーバーDBに保存します。)
- Cookieに一意識別子(UID)を入れて、ユーザーが新しいページを訪れてもショッピングカートを表示することができます。
- これを利用し、ログインページにアクセスすると、ユーザーにUIDが含まれているCookieを作成して、ユーザーにサービスを提供しています。
↓ログイン後
2.個人(検索結果の設定、テーマなど)
- ユーザーにコンテンツを表示するための情報を記憶するために使用することができます。
- 例えばGoogle検索の表示件数、サイトのユーザーテーマなどの設定があります。
3.トラッキング(ユーザーの行動)
- ユーザーが接続したときにCookieがなければ、ユーザーが訪問した最初のページと推定した後、一意の識別子を生成します。
- リクエストを送信するたびにCookieが送信されることを利用して、要求時間、滞在時間などの情報をサーバーログに保存できます。
Cookieを作成する方法
1.ユーザーがサーバーにページを要求します。
GET /test HTTP/1.1
Host: localhost:8080
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36
DNT: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: ko,en-US;q=0.9,en;q=0.8
2.サーバーからの応答と一緒にSet-Cookieヘッダーを送信します。
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Set-Cookie: CookieName1=Example1; Expires=Tue, 27-Nov-2018 02:53:13 GMT
Set-Cookie: CookieName2=Example2; Expires=Tue, 27-Nov-2018 02:53:13 GMT
Set-Cookie: JSESSIONID=8EB8434C5776358C84017077E11A3300; Path=/
Content-Type: text/html;charset=ISO-8859-1
Content-Language: ko
Content-Length: 316
- Set-Cookieは、複数件の転送が可能です。
- ヘッダでは、簡単なname:valueの形式で設定可能です。
3.ユーザーエージェントがSet-Cookieヘッダーで渡された値を持っているCookieを生成して保存します。
4.このように生成されたCookieは、クライアントがサーバーに要求するたびに、ブラウザによって一緒に送信されます。
GET /test HTTP/1.1
Host: localhost:8080
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36
DNT: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: ko,en-US;q=0.9,en;q=0.8
Cookie: JSESSIONID=8EB8434C5776358C84017077E11A3300; CookieName1=Example1; CookieName2=Example2
Cookieの属性
1. Domain
- Domainは、Cookieのスコープを定義し、Cookieがどのウェブサイトで作成されたかどうかを通知します。
- また、セキュリティ上の理由から、現在のリソースのトップレベルドメインとサブドメインのみ設定可能です。
- EX)hangame.comはpayco.comドメインのCookieを生成することができません。
- サーバー側で設定されなければ、Cookieが作成されるときの要請ドメインに設定されます。
- Domainがあるものとないもので違いが存在します。
- hangame.comでCookieを作成するときDomain値を指定しない場合
- hangame.comのみCookieが送信されます。
- hangame.comでCookieを作成するときにDomain値をhangame.comに指定した場合
- accounts.hangame.comようなサブドメインでも送信されます。
2. Path
- Domainと同様に、Cookieのスコープを定義し、Cookieは、ウェブサイトのどのパスで使用されるのかを示します。
- Pathが設定されている場合、Pathが一致する場合にのみCookieを送信します。
- Pathを指定しない場合、Set-Cookieヘッダーを送信したサーバーのパスを使用します。
3. Expires / Max-Age
- ExpireとMax-Ageは、Cookieの有効期限を表す属性です。
- Expireは、日付を指定し、有効期限が過ぎると、ブラウザが削除します。
- Max-Ageは有効時間を指定し、Cookieを受け取った時間から計算し、期限切れの場合は、ブラウザが削除します。
4. Secure
- Secureは、Cookieの範囲を安全なチャネルに制限する属性です。
- ユーザーエージェントは、要求が暗号化された接続の場合のみCookieを送信します。
5. HttpOnly
- HttpOnlyはCookieの範囲をHTTPリクエストに制限します。
- HTTPテキスト内のスクリプトからCookieにアクセスできなくなります。
Cookieの種類
セッションCookie
- セッションCookieは、クライアントが終了すると、削除されます。
- ExpiresやMax-Ageを指定しなければ、クライアントが終了すると削除されるセッションCookieが生成されます。
- 逆に言えばExpiresやMax-Ageを明示すると、指定された時間まで維持可能なCookieが生成されます。
セキュアCookie
- セキュアCookieは暗号化された接続(HTTPS)のみ転送することができます。
- secureフラグを追加して作成し、HTTPSに転送するためCookieを開いて見ることを防止できます。
Http-only Cookie
- JavaScriptのようなクライアント側のAPIを介してアクセスすることができないCookieであり、XSSの脅威を排除します。
- しかし、まだcross-site tracing(XST)とcross-site request forgery(XSRF)攻撃に攻撃を受けやすくなります。
- HttpOnlyフラグをCookieに追加して作成します。
Same-site Cookie
- Google Chromeのバージョン51で新たに導入されたCookieです。
- Cookieを作成したドメインと同じソースである場合にのみ、Cookieを送信します。
third-party Cookie
- 現在訪問しているサイトではなく、他のサイトのCookieです。
- 広告のような外部のウェブサイトのコンテンツがある場合に生成され、このCookieを利用してユーザーを追跡して、広告を提供するために使用します。
ゾンビCookie
- 消されても、自動的に生成されたCookieです。
- Cookieの内容をフラッシュCookie、HTML5のWebストア、クライアントなどに保存して、Cookieがなければ再びCookieを作成します。
Cookieのバージョンごとの違い
Cookieバージョン0、1の差
- Cookieのバージョン0はNetscape Cookieを、Cookieバージョン1はRFC 2109 Cookieを意味します。
- RFC 2109は、既存のNetscape Cookieのスペックを体系的に整理して修正しようと試みたものです。[3]
- Cookieのバージョン固有のプロパティの違い
- Netscape Cookie属性は、Cookie Name、Value、Expires、Domain、Path、Secure、があります。
- RFC 2109のCookie属性は、Cookie Name、Value、Comment、Domain、Max-Age、Path、Secure、Versionがあります。
- Cookieのバージョン別の違い
- Netscapeは、固定された長さの組み合わせの値で、有効期間を示すExpiresを使用して、RFC 2109 Cookieはdelta-secondsを使用します。
- Netscapeの日付値に空白が入ります。
- Netscape Cookieは; ,‘ ‘の みを許可していなかったが、RFC 2109には、より多く様々な特殊文字が許可されていません。
- Netscape Cookieは、属性 – 値のペア」で包括される文字列を許可していないが、RFC 2109 Cookieは許可します。
- Netscape Cookieは属性=値で=の前後にスペースを許可していないが、RFC 2109 Cookieは許可します。
RFC 6265、RFC 2109のCookieの違い
- RFC 2109は、最初Cookieの概念と文法についてのみ説明しましたが、RFC 6265はインターネット上で実際にどのように実装して使用するかを説明しました。
- RFC 6265が出RFC 2109とRFC 2965は廃止されました。[4]
- 両方のSet-Cookieヘッダーを使用します。
- RFC 2109のCookie属性は、Cookie Name、Value、Comment、Domain、Max-Age、Path、Secure、Versionがあります。
- RFC 6265のCookie属性は、Cookie Name、Value、Expires、Domain、Max-Age、Path、Secure、HttpOnlyがあります。
- Commentは、サーバーがCookieの使用を記録しておくためのプロパティ、Versionは、Cookieがどの明細書に従うかバージョンを示します。
- ExpiresはDate値を持っており、この日が経過すると、Cookieを捨て、HttpOnlyは非HTTP要求を防ぎます。(クライアントがAPIでアクセスする行為など)
- 実装時の考慮事項に関する点も変更されました。
- RFC 2109は、ユーザーエージェントが最小300個のCookie、Cookieごとに少なくとも4096バイト、1ホストやドメインごとに最小20個をサポートすると明示しました。
- RFC 6265は、ユーザーエージェントが最小3000個のCookie、Cookieごとに少なくとも4096バイト、ドメイン最小50個のCookieを保存可能であると明示しました。また、RFC 6265は、属性名とセミコロンの間に空白が必要であり、特殊文字がある場合を除いて、属性値を “”に含みません。
- RFC 6265は、ユーザーエージェントがSet-Cookieヘッダーをどのように処理するかのアルゴリズムを提供しています。
- javax.servlet.http.Cookie
* This class supports both the RFC 2109 and the RFC 6265 specifications.
* By default, cookies are created using RFC 6265.
- setVersionにnetscape、2109設定可能です。
/**
* Sets the version of the cookie protocol this cookie complies with.
* Version 0 complies with the original Netscape cookie specification.
* Version 1 complies with RFC 2109.
*
\* Since RFC 2109 is still somewhat new\, consider version 1 as experimental;
\* do not use it yet on production sites\.
\*
\* @param v
\* 0 if the cookie should comply with the original Netscape
\* specification; 1 if the cookie should comply with RFC 2109
\* @see \#getVersion
\*/
public void setVersion\(int v\) \{
version = v;
\}
- defaultバージョンのCookieを取得すると、0 (netscape)が読み込まれます。
- RFCドキュメントには、6265はExpiresが生じたと書かれていますが、maxAgeだけです。
private final String name;
private String value;
private int version = 0; // ;Version=1 ... means RFC 2109 style
//
// Attributes encoded in the header's cookie fields.
//
private String comment; // ;Comment=VALUE ... describes cookie's use
private String domain; // ;Domain=VALUE ... domain that sees cookie
private int maxAge = -1; // ;Max-Age=VALUE ... cookies auto-expire
private String path; // ;Path=VALUE ... URLs that see the cookie
private boolean secure; // ;Secure ... e.g. use SSL
private boolean httpOnly; // Not in cookie specs, but supported by browsers
- maxAgeを決めると、自動的にExpiresが決まります。
Tomcat6.0〜8.5 バージョン別のCookie問題
Tomcat6.0
6.0 Cookieバグリスト
再現方法
- 401エラーページを訪問します。
HTTP/1.1 401 Unauthorized
Pragma: No-cache
Cache-Control: no-cache
Expires: Thu, 01 Jan 1970 10:00:00 EST
WWW-Authenticate: Basic realm="Tomcat Manager Application"
Set-Cookie: JSESSIONID=removed; Path=/manager
WWW-AuthenticateREDUNDANT: Basic realm="Tomcat Manager Application"
Content-Type: text/html
Transfer-Encoding: chunked
Vary: Accept-Encoding
Date: Mon, 26 Mar 2012 03:39:09 GMT
Server: Coyote
解決方法
- 6.0.36に更新します。
- 401.jspから行を削除します。(状況に応じて異なる場合がありますので、確認が必要です。)
- もしヘッダが設定されていることを確認していない場合にのみ追加します。
- Cookieの値を””でくくると、ヘッダーの値が破損される現象
再現方法
- スクリーンショットのようにCookieを生成します。
- ページを再訪問すると、ヘッダーの値が破損します。
解決方法
- 6.0.45に更新します。
Tomcat7.0
7.0全体のバグリスト
再現方法
- ユーザーが要求を送ってユーザー認証と同時にセッションを作成します。
- JSESSIONID Cookieが返されます。
- クライアントがCookieを2番目の要求のように送信。新しいJSESSIONID Cookieが生成されます。
解決方法
- alwaysUseSession = “true”に設定します。(ユーザーがオプションとして選択可能に変更していないこと)
解決方法
- 7.0.62に更新します。
Tomcat8.0〜8.5
8.0〜8.5全体のバグリスト
1. Domain属性が.で起動すると、エラーが発生する現象
再現方法
- domainの属性を.で開始し、Cookieを作成します。
解決方法
- Domain前を除去します。
- 既存のLegacyCookieProcessorを使用するようにcontext.xml変更します。
解決方法
- 8.0.29に更新します。
解決方法
- 8.0.27に更新します。
解決方法
- 8.5.12に更新します。
- versionが0の時もRFC6265に解析できるようにします。
解決方法
- 8.5.13に更新します。
すべてのバージョンで発生
1.Cookieにハングルを保存するときにエラーが出る現象
解決方法
- URLEncoder.encodeを使用して保存します。
- Tomcatのバージョン8.5は、自動的に処理されます。
2.Cookie値に=が含まれている場合、=の後の文字が切られる現象
再現方法
- クライアント側が
=
入った値を“”に括らずに送信する場合に発生します。
- org.apache.catalina.STRICT_SERVLET_COMPIANCE=trueに設定されていて
=
が入ったCookieの値を“”に括らない場合に発生します。
解決方法
- catalina.propertiesにorg.apache.tomcat.util.http.ServerCookie.ALLOW_EQUALS_IN_VALUE=trueを追加します。
- URLEncoder.encodeを使用して保存します。(値を読み取るときにDecoding必要です。)
- valueに=が入る値は、原則通りなら無効な値なので、””の中にある必要があります。
3. Cookieの値に@が含まれている場合、@の後の文字が切られる現象
再現方法
- クライアント側@が入った値を「」に括らずに送信する場合に発生します。
- org.apache.catalina.STRICT_SERVLET_COMPLIANCE=trueに設定されていて@が入ったCookieの値を””に括らない場合に発生します。
解決方法
- catalina.propertiesにorg.apache.tomcat.util.http.ServerCookie.ALLOW_HTTP_SEPARATORS_IN_V0=trueを追加します。
- バージョン8.5
- バージョン7
- バージョン6(解決不可能)設定値がバージョン7から存在します。
- URLEncoder.encodeを使用して保存します。(値を読み取るときDecodingが必要です。)
- valueに@が入る値は、原則通りなら無効な値なので、””の中にある必要があります。
4.Cookie Valueに+が入っているときに “”(空白)に変わる現象
再現方法
- Cookieを送信する際にContent-typeがapplicatoin/x-www-form-urlencoded である場合に発生します。
解決方法
- Url-safe base64 encodeを使用します。(Encodingをする+と/は–ラング_に変更されます。)
- Java 8でjava.util.Base64のgetUrlEncoder().encodeメソッドを使用します。
- 8以下のバージョンでは、org.apache.commons.codec.binary.Base64でencodeBase64URLSafeStringメソッドを使用します。
/**
* This array is a lookup table that translates 6-bit positive integer
* index values into their "Base64 Alphabet" equivalents as specified
* in "Table 1: The Base64 Alphabet" of RFC 2045 (and RFC 4648).
*/
private static final char[] toBase64 = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
};
/**
* It's the lookup table for "URL and Filename safe Base64" as specified
* in Table 2 of the RFC 4648, with the '+' and '/' changed to '-' and
* '_'. This table is used when BASE64_URL is specified.
*/
private static final char[] toBase64URL = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
};
5.Cookieの名前に括弧が入る場合は、エラーが発生する現象
再現方法
- Cookieの名前に括弧を入れて作成します。
- Tomcat6
- 名前に括弧が入った場合valueが空白に入ります。
- valueに値が入った場合は、読み出せません。
makeCookie(response, "(CookieName1)", "Bracket");
makeCookie(response, "{CookieName2}", "Bracket");
makeCookie(response, "[CookieName3]", "Bracket");
makeCookie(response, "CookieName4", "(Bracket)");
makeCookie(response, "CookieName5", "{Bracket}");
makeCookie(response, "CookieName6", "[Bracket]");
結果
-
- Tomcat 7以上
解決方法
- URLEncoder.encodeを使用して保存します。(値を読み取るときDecodingが必要です。)
ソース
[1] https://en.wikipedia.org/wiki/HTTP_cookie#History
[2] http://web.archive.org/web/20020803110822/http://wp.netscape.com/newsref/std/cookie_spec .html
[3] https://tools.ietf.org/html/rfc2109
[4] https://tools.ietf.org/html/rfc6265