Cookie

HTTP cookies,通常稱之為“cookie”,已經(jīng)存在很長時間了,但是仍然沒有被充分理解。首要問題是存在許多誤解,認(rèn)為 cookie 是后門程序或病毒,卻忽視了其工作原理。第二個問題是,對于 cookie 的操作缺少統(tǒng)一的接口。盡管存在這些問題,cookie 仍舊在 Web 開發(fā)中扮演者重要的角色,以至于如果沒有出現(xiàn)相應(yīng)的代替品就消失的話,我們許多喜歡的 Web 應(yīng)用將變的不可用。

cookie 的起源

早期的 Web 應(yīng)用面臨的最大問題之一就是如何維持狀態(tài)。簡言之,服務(wù)器無法知道兩個請求是否來自于同一個瀏覽器。當(dāng)時,最簡單的辦法就是在請求的頁面中插入一個 token,然后在下次請求時將這個 token 返回至服務(wù)器。這需要在頁面的 form 表單中插入一個包含 token 的隱藏域,或者將 token 放在 URL 的 query 字符串中來傳遞。這兩種方法都需要手動操作,而且極易出錯。

cookie 是什么

簡單地說,cookie 就是瀏覽器儲存在用戶電腦上的一小段文本文件。cookie 是純文本格式,不包含任何可執(zhí)行的代碼。一個 Web 頁面或服務(wù)器告知瀏覽器按照一定規(guī)范來儲存這些信息,并在隨后的請求中將這些信息發(fā)送至服務(wù)器,Web 服務(wù)器就可以使用這些信息來識別不同的用戶。大多數(shù)需要登錄的網(wǎng)站在用戶驗(yàn)證成功之后都會設(shè)置一個 cookie,只要這個 cookie 存在并可以,用戶就可以自由瀏覽這個網(wǎng)站的任意頁面。再次說明,cookie 只包含數(shù)據(jù),就其本身而言并不有害。

創(chuàng)建 cookie

Web 服務(wù)器通過發(fā)送一個稱為 Set-Cookie 的 HTTP 消息頭來創(chuàng)建一個 cookie,Set-Cookie消息頭是一個字符串,其格式如下(中括號中的部分是可選的):

Set-Cookie: value[; expires=date][; domain=domain][; path=path][; secure]

消息頭的第一部分,value 部分,通常是一個 name=value格式的字符串。事實(shí)上,這種格式是原始規(guī)范中指定的格式,但是瀏覽器并不會對 cookie 值按照此格式來驗(yàn)證。實(shí)際上,你可以指定一個不含等號的字符串,它同樣會被存儲。然而,最常用的使用方式是按照 name=value格式來指定 cookie 的值(大多數(shù)接口只支持該格式)。
當(dāng)存在一個 cookie,并允許設(shè)置可選項(xiàng),該 cookie 的值會在隨后的每次請求中被發(fā)送至服務(wù)器,cookie 的值被存儲在名為 Cookie 的 HTTP 消息頭中,并且只包含了 cookie 的值,忽略全部設(shè)置選項(xiàng)。例如:

Cookie: value

通過 Set-Cookie指定的可選項(xiàng)只會在瀏覽器端使用,而不會被發(fā)送至服務(wù)器端。發(fā)送至服務(wù)器的 cookie 的值與通過 Set-Cookie指定的值完全一樣,不會有進(jìn)一步的解析或轉(zhuǎn)碼操作。如果請求中包含多個 cookie,它們將會被分號和空格分開,例如:

Cookie: value1; value2; name1=value1

服務(wù)器端框架通常包含解析 cookie 的方法,可以通過編程的方式獲取 cookie 的值。

cookie 編碼

對于 cookie 的值進(jìn)行編碼一直都存在一些困惑。普遍認(rèn)為 cookie 的值必須經(jīng)過 URL 編碼,但其實(shí)這是一個謬論,盡管通常都這么做。原始規(guī)范中明確指出只有三個字符必須進(jìn)行編碼:分號、逗號和空格,規(guī)范中還提到可以進(jìn)行 URL 編碼,但并不是必須,在 RFC 中沒有提及任何編碼。然而,幾乎所有的實(shí)現(xiàn)都對 cookie 的值進(jìn)行了一系列的 URL 編碼。對于 name=value格式,通常會對 name和 value 分別進(jìn)行編碼,而不對等號 = 進(jìn)行編碼操作。

過期時間選項(xiàng)

緊跟 cookie 值后面的每個選項(xiàng)都以分號和空格分開,每個選擇都指定了 cookie 在什么情況下應(yīng)該被發(fā)送至服務(wù)器。第一個選項(xiàng)是過期時間(expires),指定了 cookie 何時不會再被發(fā)送至服務(wù)器,隨后瀏覽器將刪除該 cookie。該選項(xiàng)的值是一個 Wdy, DD-Mon-YYYY HH:MM:SS GMT 日期格式的值,例如:

Set-Cookie: name=Nicholas; expires=Sat, 02 May 2009 23:38:25 GMT

沒有設(shè)置 expires選項(xiàng)時,cookie 的生命周期僅限于當(dāng)前會話中,關(guān)閉瀏覽器意味著這次會話的結(jié)束,所以會話 cookie 僅存在于瀏覽器打開狀態(tài)之下。這就是為什么為什么當(dāng)你登錄一個 Web 應(yīng)用時經(jīng)常會看到一個復(fù)選框,詢問你是否記住登錄信息:如果你勾選了復(fù)選框,那么一個 expires 選項(xiàng)會被附加到登錄 cookie 中。如果 expires 設(shè)置了一個過去的時間點(diǎn),那么這個 cookie 會被立即刪掉。

domain 選項(xiàng)

下一個選項(xiàng)是 domain,指定了 cookie 將要被發(fā)送至哪個或哪些域中。默認(rèn)情況下,domain會被設(shè)置為創(chuàng)建該 cookie 的頁面所在的域名,所以當(dāng)給相同域名發(fā)送請求時該 cookie 會被發(fā)送至服務(wù)器。例如,本博中 cookie 的默認(rèn)值將是 bubkoo.com。domain 選項(xiàng)可用來擴(kuò)充 cookie 可發(fā)送域的數(shù)量,例如:

Set-Cookie: name=Nicholas; domain=nczonline.net

像 Yahoo! 這種大型網(wǎng)站,都會有許多 name.yahoo.com 形式的站點(diǎn)(例如:my.yahoo.com, finance.yahoo.com 等等)。將一個 cookie 的 domain 選項(xiàng)設(shè)置為 yahoo.com,就可以將該 cookie 的值發(fā)送至所有這些站點(diǎn)。瀏覽器會把 domain 的值與請求的域名做一個尾部比較(即從字符串的尾部開始比較),并將匹配的 cookie 發(fā)送至服務(wù)器。
domain 選項(xiàng)的值必須是發(fā)送 Set-Cookie 消息頭的主機(jī)名的一部分,例如我不能在 google.com 上設(shè)置一個 cookie,因?yàn)檫@會產(chǎn)生安全問題。不合法的 domain
選擇將直接被忽略。

path 選項(xiàng)

另一個控制 Cookie消息頭發(fā)送時機(jī)的選項(xiàng)是 path選項(xiàng),和 domain選項(xiàng)類似,path選項(xiàng)指定了請求的資源 URL 中必須存在指定的路徑時,才會發(fā)送Cookie 消息頭。這個比較通常是將 path 選項(xiàng)的值與請求的 URL 從頭開始逐字符比較完成的。如果字符匹配,則發(fā)送 Cookie 消息頭,例如:

Set-Cookie:name=Nicholas;path=/blog

在這個例子中,path 選項(xiàng)值會與 /blog,/blogrool 等等相匹配;任何以 /blog 開頭的選項(xiàng)都是合法的。需要注意的是,只有在 domain 選項(xiàng)核實(shí)完畢之后才會對 path 屬性進(jìn)行比較。path 屬性的默認(rèn)值是發(fā)送 Set-Cookie 消息頭所對應(yīng)的 URL 中的 path 部分。

secure 選項(xiàng)

最后一個選項(xiàng)是 secure。不像其它選項(xiàng),該選項(xiàng)只是一個標(biāo)記而沒有值。只有當(dāng)一個請求通過 SSL 或 HTTPS 創(chuàng)建時,包含 secure 選項(xiàng)的 cookie 才能被發(fā)送至服務(wù)器。這種 cookie 的內(nèi)容具有很高的價值,如果以純文本形式傳遞很有可能被篡改,例如:

Set-Cookie: name=Nicholas; secure

事實(shí)上,機(jī)密且敏感的信息絕不應(yīng)該在 cookie 中存儲或傳輸,因?yàn)?cookie 的整個機(jī)制原本都是不安全的。默認(rèn)情況下,在 HTTPS 鏈接上傳輸?shù)?cookie 都會被自動添加上 secure 選項(xiàng)。

Cookie 的維護(hù)和生命周期

在一個 cookie 中可以指定任意數(shù)量的選項(xiàng),并且這些選項(xiàng)可以是任意順序,例如:

Set-Cookie:name=Nicholas; domain=nczonline.net; path=/blog

這個 cookie 有四個標(biāo)識符:cookie 的 name,domain,path
,secure 標(biāo)記。要想改變這個 cookie 的值,需要發(fā)送另一個具有相同 cookie name,domain,path 的 Set-Cookie 消息頭。例如:

Set-Cookie: name=Greg; domain=nczonline.net; path=/blog

這將覆蓋原來 cookie 的值。但是,修改 cookie 選項(xiàng)的任意一項(xiàng)都將創(chuàng)建一個完全不同的新 cookie,例如:

Set-Cookie: name=Nicholas; domain=nczonline.net; path=/

這個消息頭返回之后,會同時存在兩個名為 “name” 的不同的 cookie。如果你訪問 www.nczonline.net/blog 下的一個頁面,以下的消息頭將被包含進(jìn)來:

Cookie: name=Greg; name=Nicholas

在這個消息頭中存在了兩個名為 “name” 的 cookie,path 值越詳細(xì)則 cookie 越靠前。 按照 domain-path-secure 的順序,設(shè)置越詳細(xì)的 cookie 在字符串中越靠前。假設(shè)我在 ww.nczonline.net/blog 下用默認(rèn)選項(xiàng)創(chuàng)建了另一個 cookie:

Set-Cookie: name=Mike

那么返回的消息頭現(xiàn)在則變?yōu)椋?/p>

Cookie: name=Mike; name=Greg; name=Nicholas

以 “Mike” 作為值的 cookie 使用了域名(www.nczonline.net
)作為其 domain 值并且以全路徑(/blog)作為其 path 值,則它較其它兩個 cookie 更加詳細(xì)。

使用失效日期

當(dāng) cookie 創(chuàng)建時指定了失效日期,這個失效日期則關(guān)聯(lián)了以 name-domain-path-secure 為標(biāo)識的 cookie。要改變一個 cookie 的失效日期,你必須指定同樣的組合。當(dāng)改變一個 cookie 的值時,你不必每次都設(shè)置失效日期,因?yàn)樗皇?cookie 標(biāo)識信息的組成部分。例如:

Set-Cookie:name=Mike;expires=Sat,03 May 2025 17:44:22 GMT

現(xiàn)在已經(jīng)設(shè)置了 cookie 的失效日期,所以下次我想要改變 cookie 的值時,我只需要使用它的名字:

Set-Cookie:name=Matt

cookie 的失效日期并沒有改變,因?yàn)?cookie 的標(biāo)識符是相同的。實(shí)際上,只有你手工的改變 cookie 的失效日期,否則其失效日期不會改變。這意味著在同一個會話中,一個會話 cookie 可以變成一個持久化 cookie(一個可以在多個會話中存在的),反之則不可。為了要將一個持久化 cookie 變?yōu)橐粋€會話 cookie,你必須刪除這個持久化 cookie,這只要設(shè)置它的失效日期為過去某個時間之后再創(chuàng)建一個同名的會話 cookie 就可以實(shí)現(xiàn)。
需要記得的是失效日期是以瀏覽器運(yùn)行的電腦上的系統(tǒng)時間為基準(zhǔn)進(jìn)行核實(shí)的。沒有任何辦法來來驗(yàn)證這個系統(tǒng)時間是否和服務(wù)器的時間同步,所以當(dāng)服務(wù)器時間和瀏覽器所處系統(tǒng)時間存在差異時這樣的設(shè)置會出現(xiàn)錯誤。

cookie 自動刪除

cookie 會被瀏覽器自動刪除,通常存在以下幾種原因:
會話 cooke (Session cookie) 在會話結(jié)束時(瀏覽器關(guān)閉)會被刪除
持久化 cookie(Persistent cookie)在到達(dá)失效日期時會被刪除
如果瀏覽器中的 cookie 數(shù)量達(dá)到限制,那么 cookie 會被刪除以為新建的 cookie 創(chuàng)建空間。
對于自動刪除來說,Cookie 管理顯得十分重要,因?yàn)檫@些刪除都是無意識的。

Cookie 限制條件

cookie 存在許多限制條件,來阻止 cookie 濫用并保護(hù)瀏覽器和服務(wù)器免受一些負(fù)面影響。有兩種 cookie 限制條件:cookie 的屬性和 cookie 的總大小。原始規(guī)范中限定每個域名下不超過 20 個 cookie,早期的瀏覽器都遵循該規(guī)范,并且在 IE7 中有更近一步的提升。在微軟的一次更新中,他們在 IE7 中增加 cookie 的限制數(shù)量到 50 個,與此同時 Opera 限定 cookie 數(shù)量為 30 個,Safari 和 Chrome 對與每個域名下的 cookie 個數(shù)沒有限制。
發(fā)向服務(wù)器的所有 cookie 的最大數(shù)量(空間)仍舊維持原始規(guī)范中所指出的:4KB。所有超出該限制的 cookie 都會被截掉并且不會發(fā)送至服務(wù)器。

Subcookies

鑒于 cookie 的數(shù)量存在限制,開發(fā)者提出 subcookies 的觀點(diǎn)來增加 cookie 的存儲量。Subcookies 是存儲在一個 cookie 值中的一些 name-value
對,通常與以下格式類似:

name=a=b&c=d&e=f&g=h

這種方式允許在單個 cookie 中保存多個 name-value 對,而不會超出瀏覽器 cookie 數(shù)量的限制。通過這種方式創(chuàng)建 cookie 的負(fù)面影響是,需要自定義解析方式來提取這些值,相比較而言 cookie 的格式會更為簡單。服務(wù)器端框架已開始支持 subcookies 的存儲。我編寫的 YUI Cookie utility,支持在 javascript 中讀/寫 subcookies

JavaScript 中的 cookie

在 JavaScript 中通過 document.cookie 屬性,你可以創(chuàng)建、維護(hù)和刪除 cookie。創(chuàng)建 cookie 時該屬性等同于 Set-Cookie 消息頭,而在讀取 cookie 時則等同于 Cookie 消息頭。在創(chuàng)建一個 cookie 時,你需要使用和 Set-Cookie 期望格式相同的字符串:

document.cookie="name=Nicholas;domain=nczonline.net;path=/";

設(shè)置 document.cookie 屬性的值并不會刪除存儲在頁面中的所有 cookie。它只簡單的創(chuàng)建或修改字符串中指定的 cookie。下次發(fā)送一個請求到服務(wù)器時,通過 document.cookie 設(shè)置的 cookie 會和其它通過 Set-Cookie 消息頭設(shè)置的 cookie 一并發(fā)送至服務(wù)器。這些 cookie 并沒有什么明確的不同之處。要使用 JavaScript 提取 cookie 的值,只需要從 document.cookie 中讀取即可。返回的字符串與 Cookie 消息頭中的字符串格式相同,所以多個 cookie 會被分號和字符串分割。例如:

name1=Greg; name2=Nicholas

鑒于此,你需要手工解析這個 cookie 字符串來提取真實(shí)的 cookie 數(shù)據(jù)。當(dāng)前已有許多描述如何利用 JavaScript 來解析 cookie 的資料,包括我的書,Professional JavaScript,所以在這我就不再說明。通常利用已存在的 JavaScript 庫操作 cookie 會更簡單,如使用 YUI Cookie utility 來處理 cookie,而不要手工重新創(chuàng)建這些算法。
通過訪問 document.cookie 返回的 cookie 遵循發(fā)向服務(wù)器的 cookie 一樣的訪問規(guī)則。要通過 JavaScript 訪問 cookie,該頁面和 cookie 必須在相同的域中,有相同的 path,有相同的安全級別。
注意:一旦 cookie 通過 JavaScript 設(shè)置后便不能提取它的選項(xiàng),所以你將不能知道 domain,path,expires 日期或 secure 標(biāo)記。

HTTP-Only cookies

微軟的 IE6 SP1 在 cookie 中引入了一個新的選項(xiàng):HTTP-only,HTTP-Only 背后的意思是告之瀏覽器該 cookie 絕不能通過 JavaScript 的 document.cookie 屬性訪問。設(shè)計(jì)該特征意在提供一個安全措施來幫助阻止通過 JavaScript 發(fā)起的跨站腳本攻擊 (XSS) 竊取 cookie 的行為(我會在另一篇博客中討論安全問題,本篇如此已足夠)。今天 Firefox2.0.0.5+、Opera9.5+、Chrome 都支持 HTTP-Only cookie。3.2 版本的 Safari 仍不支持。
要創(chuàng)建一個 HTTP-Only cookie,只要向你的 cookie 中添加一個 HTTP-Only
標(biāo)記即可:

Set-Cookie: name=Nicholas; HttpOnly

一旦設(shè)定這個標(biāo)記,通過 documen.coookie 則不能再訪問該 cookie。IE 同時更近一步并且不允許通過 XMLHttpRequest 的 getAllResponseHeaders()
或 getResponseHeader() 方法訪問 cookie,然而其它瀏覽器則允許此行為。Firefox 在 3.0.6 中修復(fù)了該漏洞,然而仍舊有許多瀏覽器漏洞存在,complete browser support list 列出了這些。
你不能通過 JavaScript 設(shè)置 HTTP-only
,因?yàn)槟悴荒茉偻ㄟ^ JavaScript 讀取這些 cookie,這是情理之中的事情。

總結(jié)

為了高效的利用 cookie,仍舊有許多要了解和弄明白的東西。對于一項(xiàng)創(chuàng)建于十多年前但仍舊如最初實(shí)現(xiàn)的那樣被使用至今的技術(shù)來說,這是件多不可思議的事。本篇只是提供了一些每個人都應(yīng)該知道的關(guān)于瀏覽器 cookie 的基本指導(dǎo),但無論如何,也不是一個完整的參考。對于今天的 Web 來說 cookie 仍舊起著非常重要的作用,并且不恰當(dāng)?shù)墓芾?cookie 會導(dǎo)致各種各樣的問題,從最糟糕的用戶體驗(yàn)到安全漏洞。我希望這篇手冊能夠激起一些關(guān)于 cookie 的不可思議的亮點(diǎn)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內(nèi)容