Cookie

原文鏈接:https://segmentfault.com/a/1190000004556040

記得跟Nodejs + Cookie 這篇整合一下。

我自己創(chuàng)建了一個(gè)網(wǎng)站,網(wǎng)址為http://ppsc.sanai.com。在這個(gè)網(wǎng)頁中我設(shè)置了幾個(gè)cookieJSSESSIONID,PA_VTIMEskmtutc,test。

在 chrome 瀏覽器中打開這個(gè)網(wǎng)站,進(jìn)入開發(fā)者模式,點(diǎn)擊Resources欄 -> 選擇cookies,我們會(huì)看到如下圖所示的界面:

image.png

解釋一下:左邊欄Cookies下方會(huì)列舉當(dāng)前網(wǎng)頁中設(shè)置過cookie的域都有哪些。上圖中只有一個(gè)域,即“ppsc.sanai.com”。而右側(cè)區(qū)域顯示的就是某個(gè)域下具體的 cookie 列表,對(duì)應(yīng)上圖就是“ppsc.sanai.com”域下設(shè)置的4個(gè)cookie。

在這個(gè)網(wǎng)頁中我往http://ppsc.sanai.com/getList接口發(fā)了一個(gè) Ajax 請(qǐng)求,request header如下圖所示:

image.png

從上圖中我們會(huì)看到request header中自動(dòng)添加了Cookie字段(我并沒有手動(dòng)添加這個(gè)字段哦~),Cookie字段的值其實(shí)就是我設(shè)置的那4個(gè) cookie。這個(gè)請(qǐng)求最終會(huì)發(fā)送到http://ppsc.sanai.com這個(gè)服務(wù)器上,這個(gè)服務(wù)器就能從接收到的request header中提取那4個(gè)cookie。

上面兩張圖展示了cookie的基本通信流程:設(shè)置cookie => cookie被自動(dòng)添加到request header中 => 服務(wù)端接收到cookie。這個(gè)流程中有幾個(gè)問題需要好好研究:

  1. 什么樣的數(shù)據(jù)適合放在cookie中?

  2. cookie是怎么設(shè)置的?

  3. cookie為什么會(huì)自動(dòng)加到request header中?

  4. cookie怎么增刪查改?

我們要帶著這幾個(gè)問題繼續(xù)往下閱讀。

cookie 是怎么工作的?

首先必須明確一點(diǎn),存儲(chǔ)cookie是瀏覽器提供的功能。cookie 其實(shí)是存儲(chǔ)在瀏覽器中的純文本,瀏覽器的安裝目錄下會(huì)專門有一個(gè) cookie 文件夾來存放各個(gè)域下設(shè)置的cookie。

當(dāng)網(wǎng)頁要發(fā)http請(qǐng)求時(shí),瀏覽器會(huì)先檢查是否有相應(yīng)的cookie,有則自動(dòng)添加在request header中的cookie字段中。這些是瀏覽器自動(dòng)幫我們做的,而且每一次http請(qǐng)求瀏覽器都會(huì)自動(dòng)幫我們做。這個(gè)特點(diǎn)很重要,因?yàn)檫@關(guān)系到“什么樣的數(shù)據(jù)適合存儲(chǔ)在cookie中”。

存儲(chǔ)在cookie中的數(shù)據(jù),每次都會(huì)被瀏覽器自動(dòng)放在http請(qǐng)求中,如果這些數(shù)據(jù)并不是每個(gè)請(qǐng)求都需要發(fā)給服務(wù)端的數(shù)據(jù),瀏覽器這設(shè)置自動(dòng)處理無疑增加了網(wǎng)絡(luò)開銷;但如果這些數(shù)據(jù)是每個(gè)請(qǐng)求都需要發(fā)給服務(wù)端的數(shù)據(jù)(比如身份認(rèn)證信息),瀏覽器這設(shè)置自動(dòng)處理就大大免去了重復(fù)添加操作。所以對(duì)于那設(shè)置“每次請(qǐng)求都要攜帶的信息(最典型的就是身份認(rèn)證信息)”就特別適合放在cookie中,其他類型的數(shù)據(jù)就不適合了。

但在 localStorage 出現(xiàn)之前,cookie被濫用當(dāng)做了存儲(chǔ)工具。什么數(shù)據(jù)都放在cookie中,即使這些數(shù)據(jù)只在頁面中使用而不需要隨請(qǐng)求傳送到服務(wù)端。當(dāng)然cookie標(biāo)準(zhǔn)還是做了一些限制的:每個(gè)域名下的cookie 的大小最大為4KB,每個(gè)域名下的cookie數(shù)量最多為20個(gè)(但很多瀏覽器廠商在具體實(shí)現(xiàn)時(shí)支持大于20個(gè))。

cookie 的格式

document.cookie

JS 原生的 API提供了獲取cookie的方法:document.cookie(注意,這個(gè)方法只能獲取非 HttpOnly 類型的cookie)。在 console 中執(zhí)行這段代碼可以看到結(jié)果如下圖:

image.png

打印出的結(jié)果是一個(gè)字符串類型,因?yàn)?code>cookie本身就是存儲(chǔ)在瀏覽器中的字符串。但這個(gè)字符串是有格式的,由鍵值對(duì) key=value構(gòu)成,鍵值對(duì)之間由一個(gè)分號(hào)和一個(gè)空格隔開。

cookie 的屬性選項(xiàng)

每個(gè)cookie都有一定的屬性,如什么時(shí)候失效,要發(fā)送到哪個(gè)域名,哪個(gè)路徑等等。這些屬性是通過cookie選項(xiàng)來設(shè)置的,cookie選項(xiàng)包括:expires、domain、path、secureHttpOnly。在設(shè)置任一個(gè)cookie時(shí)都可以設(shè)置相關(guān)的這些屬性,當(dāng)然也可以不設(shè)置,這時(shí)會(huì)使用這些屬性的默認(rèn)值。在設(shè)置這些屬性時(shí),屬性之間由一個(gè)分號(hào)和一個(gè)空格隔開。代碼示例如下:

"key=name; expires=Thu, 25 Feb 2016 04:18:00 GMT; domain=ppsc.sanai.com; path=/; secure; HttpOnly"

expires

expires選項(xiàng)用來設(shè)置“cookie 什么時(shí)間內(nèi)有效”。expires其實(shí)是cookie失效日期,expires必須是 GMT 格式的時(shí)間(可以通過new Date().toGMTString()或者new Date().toUTCString() 來獲得)。

expires=Thu, 25 Feb 2016 04:18:00 GMT表示cookie講在2016年2月25日4:18分之后失效,對(duì)于失效的cookie瀏覽器會(huì)清空。如果沒有設(shè)置該選項(xiàng),則默認(rèn)有效期為session,即會(huì)話cookie。這種cookie在瀏覽器關(guān)閉后就沒有了。

expires 是 http/1.0協(xié)議中的選項(xiàng),在新的http/1.1協(xié)議中expires已經(jīng)由 max-age 選項(xiàng)代替,兩者的作用都是限制cookie 的有效時(shí)間。expires的值是一個(gè)時(shí)間點(diǎn)(cookie失效時(shí)刻= expires),而max-age 的值是一個(gè)以為單位時(shí)間段(cookie失效時(shí)刻= 創(chuàng)建時(shí)刻+ max-age)。
另外,max-age 的默認(rèn)值是 -1(即有效期為 session );若max-age有三種可能值:負(fù)數(shù)、0、正數(shù)。負(fù)數(shù):有效期session;0:刪除cookie;正數(shù):有效期為創(chuàng)建時(shí)刻+ max-age

domain 和 path

domain是域名,path是路徑,兩者加起來就構(gòu)成了 URL,domainpath一起來限制 cookie 能被哪些 URL 訪問。

一句話概括:某cookie的 domain為“baidu.com”, path為“/ ”,若請(qǐng)求的URL(URL 可以是js/html/img/css資源請(qǐng)求,但不包括 XHR 請(qǐng)求)的域名是“baidu.com”或其子域如“api.baidu.com”、“dev.api.baidu.com”,且 URL 的路徑是“/ ”或子路徑“/home”、“/home/login”,則瀏覽器會(huì)將此 cookie 添加到該請(qǐng)求的 cookie 頭部中。

所以domainpath2個(gè)選項(xiàng)共同決定了cookie何時(shí)被瀏覽器自動(dòng)添加到請(qǐng)求頭部中發(fā)送出去。如果沒有設(shè)置這兩個(gè)選項(xiàng),則會(huì)使用默認(rèn)值。domain的默認(rèn)值為設(shè)置該cookie的網(wǎng)頁所在的域名,path默認(rèn)值為設(shè)置該cookie的網(wǎng)頁所在的目錄。

特別說明1:
發(fā)生跨域xhr請(qǐng)求時(shí),即使請(qǐng)求URL的域名和路徑都滿足 cookie 的 domain 和 path,默認(rèn)情況下cookie也不會(huì)自動(dòng)被添加到請(qǐng)求頭部中。若想知道原因請(qǐng)閱讀本文最后一節(jié))

特別說明2:
domain是可以設(shè)置為頁面本身的域名(本域),或頁面本身域名的父域,但不能是公共后綴 public suffix。舉例說明下:如果頁面域名為 www.baidu.com, domain可以設(shè)置為“www.baidu.com”,也可以設(shè)置為“baidu.com”,但不能設(shè)置為“.com”或“com”。

secure

secure選項(xiàng)用來設(shè)置cookie只在確保安全的請(qǐng)求中才會(huì)發(fā)送。當(dāng)請(qǐng)求是HTTPS或者其他安全協(xié)議時(shí),包含 secure 選項(xiàng)的 cookie 才能被發(fā)送至服務(wù)器。

默認(rèn)情況下,cookie不會(huì)帶secure選項(xiàng)(即為空)。所以默認(rèn)情況下,不管是HTTPS協(xié)議還是HTTP協(xié)議的請(qǐng)求,cookie 都會(huì)被發(fā)送至服務(wù)端。但要注意一點(diǎn),secure選項(xiàng)只是限定了在安全情況下才可以傳輸給服務(wù)端,但并不代表你不能看到這個(gè) cookie。

下面我們?cè)O(shè)置一個(gè) secure類型的 cookie:

document.cookie = "name=huang; secure";

之后你就能在控制臺(tái)中看到這個(gè) cookie 了,如下圖所示:


image.png

這里有個(gè)坑需要注意下:
如果想在客戶端即網(wǎng)頁中通過 js 去設(shè)置secure類型的 cookie,必須保證網(wǎng)頁是https協(xié)議的。在http協(xié)議的網(wǎng)頁中是無法設(shè)置secure類型cookie的。

httpOnly

這個(gè)選項(xiàng)用來設(shè)置cookie是否能通過 js 去訪問。默認(rèn)情況下,cookie不會(huì)帶httpOnly選項(xiàng)(即為空),所以默認(rèn)情況下,客戶端是可以通過js代碼去訪問(包括讀取、修改、刪除等)這個(gè)cookie的。當(dāng)cookiehttpOnly選項(xiàng)時(shí),客戶端則無法通過js代碼去訪問(包括讀取、修改、刪除等)這個(gè)cookie。

在客戶端是不能通過js代碼去設(shè)置一個(gè)httpOnly類型的cookie的,這種類型的cookie只能通過服務(wù)端來設(shè)置。

那我們?cè)陧撁嬷性趺粗滥男?code>cookie是httpOnly類型的呢?看下圖:

image.png

凡是httpOnly類型的cookie,其 HTTP 一列都會(huì)打上√,如上圖中的PA_VTIME。你通過document.cookie是不能獲取的,也不能修改PA_VTIME的。

——httpOnly與安全

從上面介紹中,大家是否會(huì)有這樣的疑問:為什么我們要限制客戶端去訪問cookie?其實(shí)這樣做是為了保障安全。

試想:如果任何 cookie 都能被客戶端通過document.cookie獲取會(huì)發(fā)生什么可怕的事情。當(dāng)我們的網(wǎng)頁遭受了 XSS 攻擊,有一段惡意的script腳本插到了網(wǎng)頁中。這段script腳本做的事情是:通過document.cookie讀取了用戶身份驗(yàn)證相關(guān)的 cookie,并將這些 cookie 發(fā)送到了攻擊者的服務(wù)器。攻擊者輕而易舉就拿到了用戶身份驗(yàn)證信息,于是就可以搖搖大擺地冒充此用戶訪問你的服務(wù)器了(因?yàn)楣粽哂泻戏ǖ挠脩羯矸蒡?yàn)證信息,所以會(huì)通過你服務(wù)器的驗(yàn)證)。

如何設(shè)置 cookie?

知道了cookie的格式,cookie的屬性選項(xiàng),接下來我們就可以設(shè)置cookie了。首先得明確一點(diǎn):cookie既可以由服務(wù)端來設(shè)置,也可以由客戶端來設(shè)置。

服務(wù)端設(shè)置 cookie

不管你是請(qǐng)求一個(gè)資源文件(如 html/js/css/圖片),還是發(fā)送一個(gè)ajax請(qǐng)求,服務(wù)端都會(huì)返回response。而response header中有一項(xiàng)叫set-cookie,是服務(wù)端專門用來設(shè)置cookie的。如下圖所示,服務(wù)端返回的response header中有5個(gè)set-cookie字段,每個(gè)字段對(duì)應(yīng)一個(gè)cookie(注意不能將多個(gè)cookie放在一個(gè)set-cookie字段中),set-cookie字段的值就是普通的字符串,每個(gè)cookie還設(shè)置了相關(guān)屬性選項(xiàng)。

image.png

注意:

  • 一個(gè)set-Cookie字段只能設(shè)置一個(gè)cookie,當(dāng)你要想設(shè)置多個(gè) cookie,需要添加同樣多的set-Cookie字段。

  • 服務(wù)端可以設(shè)置cookie 的所有選項(xiàng):expires、domain、pathsecure、HttpOnly

客戶端設(shè)置 cookie

在網(wǎng)頁即客戶端中我們也可以通過js代碼來設(shè)置cookie。如我當(dāng)前打開的網(wǎng)址為http://dxw.st.sanai.com/mp/,在控制臺(tái)中我們執(zhí)行了下面代碼:

document.cookie = "name=Jonh; ";

查看瀏覽器 cookie 面板如下圖所示,cookie確實(shí)設(shè)置成功了,而且屬性選項(xiàng) domainpath、expires都用了默認(rèn)值。

image.png

再執(zhí)行下面代碼:

document.cookie="age=12; expires=Thu, 26 Feb 2116 11:50:25 GMT; domain=sanai.com; path=/";

查看瀏覽器cookie 面板,如下圖所示,新的cookie設(shè)置成功了,而且屬性選項(xiàng) domain、path、expires都變成了設(shè)定的值。

image.png

注意:

  • 客戶端可以設(shè)置cookie 的下列選項(xiàng):expires、domain、path、secure(有條件:只有在https協(xié)議的網(wǎng)頁中,客戶端設(shè)置secure類型的 cookie 才能成功),但無法設(shè)置HttpOnly選項(xiàng)。

用 js 如何設(shè)置多個(gè) cookie

當(dāng)要設(shè)置多個(gè)cookie時(shí), js 代碼很自然地我們會(huì)這么寫:

document.cookie = "name=Jonh; age=12; class=111";

但你會(huì)發(fā)現(xiàn)這樣寫只是添加了第一個(gè)cookie“name=John”,后面的所有cookie都沒有添加成功。所以最簡(jiǎn)單的設(shè)置多個(gè)cookie的方法就在重復(fù)執(zhí)行document.cookie = "key=name",如下:

document.cookie = "name=Jonh";
document.cookie = "age=12";
document.cookie = "class=111";

如何修改、刪除

修改 cookie

要想修改一個(gè)cookie,只需要重新賦值就行,舊的值會(huì)被新的值覆蓋。但要注意一點(diǎn),在設(shè)置新cookie時(shí),path/domain這幾個(gè)選項(xiàng)一定要舊cookie 保持一樣。否則不會(huì)修改舊值,而是添加了一個(gè)新的 cookie。

刪除 cookie

刪除一個(gè)cookie 也挺簡(jiǎn)單,也是重新賦值,只要將這個(gè)新cookie的expires 選項(xiàng)設(shè)置為一個(gè)過去的時(shí)間點(diǎn)就行了。但同樣要注意,path/domain/這幾個(gè)選項(xiàng)一定要舊cookie 保持一樣。

cookie 編碼

cookie其實(shí)是個(gè)字符串,但這個(gè)字符串中逗號(hào)、分號(hào)、空格被當(dāng)做了特殊符號(hào)。所以當(dāng)cookie的 key 和 value 中含有這3個(gè)特殊字符時(shí),需要對(duì)其進(jìn)行額外編碼,一般會(huì)用escape進(jìn)行編碼,讀取時(shí)用unescape進(jìn)行解碼;當(dāng)然也可以用encodeURIComponent/decodeURIComponent或者encodeURI/decodeURI三者的區(qū)別可以參考這篇文章)。

var key = escape("name;value");
var value = escape("this is a value contain , and ;");
document.cookie= key + "=" + value + "; expires=Thu, 26 Feb 2116 11:50:25 GMT; domain=sanai.com; path=/";

跨域請(qǐng)求中 cookie

之前在介紹 XHR 的一篇文章里面提過:默認(rèn)情況下,在發(fā)生跨域時(shí),cookie 作為一種 credential 信息是不會(huì)被傳送到服務(wù)端的。必須要進(jìn)行額外設(shè)置才可以。具體原因和如何設(shè)置可以參考我的這篇文章:你真的會(huì)使用XMLHttpRequest嗎?

另外,關(guān)于跨域資源共享 CORS極力推薦大家閱讀阮一峰老師的這篇 跨域資源共享 CORS 詳解

其他補(bǔ)充

  1. 什么時(shí)候 cookie 會(huì)被覆蓋:name/domain/path 這3個(gè)字段都相同的時(shí)候;

  2. 關(guān)于domain的補(bǔ)充說明(參考1/參考2):

    1. 如果顯式設(shè)置了 domain,則設(shè)置成什么,瀏覽器就存成什么;但如果沒有顯式設(shè)置,則瀏覽器會(huì)自動(dòng)取 url 的 host 作為 domain 值;

    2. 新的規(guī)范中,顯式設(shè)置 domain 時(shí),如果 value 最前面帶點(diǎn),則瀏覽器處理時(shí)會(huì)將這個(gè)點(diǎn)去掉,所以最后瀏覽器存的就是沒有點(diǎn)的(注意:但目前大多數(shù)瀏覽器并未全部這么實(shí)現(xiàn))

    3. 前面帶點(diǎn)‘.’和不帶點(diǎn)‘.’有啥區(qū)別:

      • 帶點(diǎn):任何 subdomain 都可以訪問,包括父 domain

      • 不帶點(diǎn):只有完全一樣的域名才能訪問,subdomain 不能(但在 IE 下比較特殊,它支持 subdomain 訪問)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容