我們們不搞一開始就一大堆理論知識介紹,怕把人講懵了...... 我們們換一個思維方式——"從現象看本質",先說說我們看到了什麼,再從看到的現象中提出問題,最後深入尋找答案。
我們看到的 cookie
我自己建立了一個網站,網址為http://ppsc.sankuai.com
。在這個網頁中我設定了幾個cookie
:JSSESSIONID
,PA_VTIME
,skmtutc
,test
。
在 chrome 瀏覽器中開啟這個網站,進入開發者模式,點選Resources
欄 -> 選擇cookies
,我們會看到如下圖所示的介面:
解釋一下:左邊欄Cookies
下方會列舉當前網頁中設定過cookie
的域都有哪些。上圖中只有一個域,即“ppsc.sankuai.com”。而右側區域顯示的就是某個域下具體的 cookie
列表,對應上圖就是“ppsc.sankuai.com”域下設定的4個cookie
。
在這個網頁中我往http://ppsc.sankuai.com/getList
介面發了一個 Ajax 請求,request header
如下圖所示:
從上圖中我們會看到request header
中自動新增了Cookie
欄位(我並沒有手動新增這個欄位哦~),Cookie
欄位的值其實就是我設定的那4個 cookie
。這個請求最終會傳送到http://ppsc.sankuai.com
這個伺服器上,這個伺服器就能從接收到的request header
中提取那4個cookie
。
上面兩張圖展示了cookie
的基本通訊流程:設定cookie
=> cookie
被自動新增到request header
中 => 服務端接收到cookie
。這個流程中有幾個問題需要好好研究:
什麼樣的資料適合放在
cookie
中?cookie
是怎麼設定的?cookie
為什麼會自動加到request header
中?cookie
怎麼增刪查改?
我們要帶著這幾個問題繼續往下閱讀。
cookie 是怎麼工作的?
首先必須明確一點,儲存cookie
是瀏覽器提供的功能。cookie
其實是儲存在瀏覽器中的純文字,瀏覽器的安裝目錄下會專門有一個 cookie 資料夾來存放各個域下設定的cookie
。
當網頁要發http
請求時,瀏覽器會先檢查是否有相應的cookie
,有則自動新增在request header
中的cookie
欄位中。這些是瀏覽器自動幫我們做的,而且每一次http
請求瀏覽器都會自動幫我們做。這個特點很重要,因為這關係到“什麼樣的資料適合儲存在cookie
中”。
儲存在cookie
中的資料,每次都會被瀏覽器自動放在http
請求中,如果這些資料並不是每個請求都需要發給服務端的資料,瀏覽器這設定自動處理無疑增加了網路開銷;但如果這些資料是每個請求都需要發給服務端的資料(比如身份認證資訊),瀏覽器這設定自動處理就大大免去了重複新增操作。所以對於那設定“每次請求都要攜帶的資訊(最典型的就是身份認證資訊)”就特別適合放在cookie
中,其他型別的資料就不適合了。
但在 localStorage 出現之前,cookie
被濫用當做了儲存工具。什麼資料都放在cookie
中,即使這些資料只在頁面中使用而不需要隨請求傳送到服務端。當然cookie
標準還是做了一些限制的:每個域名下的cookie 的大小最大為4KB,每個域名下的cookie
數量最多為20個(但很多瀏覽器廠商在具體實現時支援大於20個)。
cookie 的格式
document.cookie
JS 原生的 API提供了獲取cookie
的方法:document.cookie
(注意,這個方法只能獲取非 HttpOnly 型別的cookie
)。在 console 中執行這段程式碼可以看到結果如下圖:
列印出的結果是一個字串型別,因為cookie
本身就是儲存在瀏覽器中的字串。但這個字串是有格式的,由鍵值對 key=value
構成,鍵值對之間由一個分號
和一個空格
隔開。
cookie 的屬性選項
每個cookie
都有一定的屬性,如什麼時候失效,要傳送到哪個域名,哪個路徑等等。這些屬性是通過cookie
選項來設定的,cookie
選項包括:expires
、domain
、path
、secure
、HttpOnly
。在設定任一個cookie
時都可以設定相關的這些屬性,當然也可以不設定,這時會使用這些屬性的預設值。在設定這些屬性時,屬性之間由一個分號
和一個空格
隔開。程式碼示例如下:
"key=name; expires=Thu, 25 Feb 2016 04:18:00 GMT; domain=ppsc.sankuai.com; path=/; secure; HttpOnly"
expires
expires
選項用來設定“cookie 什麼時間內有效”。expires
其實是cookie
失效日期,expires
必須是 GMT
格式的時間(可以通過 new Date().toGMTString()
或者 new Date().toUTCString()
來獲得)。
如expires=Thu, 25 Feb 2016 04:18:00 GMT
表示cookie
講在2016年2月25日4:18分之後失效,對於失效的cookie
瀏覽器會清空。如果沒有設定該選項,則預設有效期為session
,即會話cookie
。這種cookie
在瀏覽器關閉後就沒有了。
expires
是 http/1.0協議中的選項,在新的http/1.1協議中expires
已經由max-age
選項代替,兩者的作用都是限制cookie 的有效時間。expires
的值是一個時間點(cookie失效時刻= expires
),而max-age
的值是一個以秒
為單位時間段(cookie失效時刻= 建立時刻+ max-age
)。
另外,max-age
的預設值是-1
(即有效期為session
);若max-age
有三種可能值:負數、0、正數。負數:有效期session
;0
:刪除cookie
;正數:有效期為建立時刻+ max-age
domain 和 path
domain
是域名,path
是路徑,兩者加起來就構成了 URL,domain
和path
一起來限制 cookie 能被哪些 URL 訪問。
一句話概括:某cookie的 domain
為“baidu.com”, path
為“/ ”,若請求的URL(URL 可以是js/html/img/css資源請求,但不包括 XHR 請求)的域名是“baidu.com”或其子域如“api.baidu.com”、“dev.api.baidu.com”,且 URL 的路徑是“/ ”或子路徑“/home”、“/home/login”,則瀏覽器會將此 cookie 新增到該請求的 cookie 頭部中。
所以domain
和path
2個選項共同決定了cookie
何時被瀏覽器自動新增到請求頭部中傳送出去。如果沒有設定這兩個選項,則會使用預設值。domain
的預設值為設定該cookie
的網頁所在的域名,path
預設值為設定該cookie
的網頁所在的目錄。
特別說明1:
發生跨域xhr請求時,即使請求URL的域名和路徑都滿足 cookie 的 domain 和 path,預設情況下cookie也不會自動被新增到請求頭部中。若想知道原因請閱讀本文最後一節)特別說明2:
domain是可以設定為頁面本身的域名(本域),或頁面本身域名的父域,但不能是公共字尾 public suffix。舉例說明下:如果頁面域名為 www.baidu.com, domain可以設定為“www.baidu.com”,也可以設定為“baidu.com”,但不能設定為“.com”或“com”。
secure
secure
選項用來設定cookie
只在確保安全的請求中才會傳送。當請求是HTTPS
或者其他安全協議時,包含 secure
選項的 cookie
才能被髮送至伺服器。
預設情況下,cookie
不會帶secure
選項(即為空)。所以預設情況下,不管是HTTPS
協議還是HTTP
協議的請求,cookie
都會被髮送至服務端。但要注意一點,secure
選項只是限定了在安全情況下才可以傳輸給服務端,但並不代表你不能看到這個 cookie。
下面我們設定一個 secure
型別的 cookie:
document.cookie = "name=huang; secure";
之後你就能在控制檯中看到這個 cookie 了,如下圖所示:
這裡有個坑需要注意下:
如果想在客戶端即網頁中通過 js 去設定secure
型別的 cookie,必須保證網頁是https
協議的。在http
協議的網頁中是無法設定secure
型別cookie的。
httpOnly
這個選項用來設定cookie
是否能通過 js
去訪問。預設情況下,cookie
不會帶httpOnly
選項(即為空),所以預設情況下,客戶端是可以通過js
程式碼去訪問(包括讀取、修改、刪除等)這個cookie
的。當cookie
帶httpOnly
選項時,客戶端則無法通過js
程式碼去訪問(包括讀取、修改、刪除等)這個cookie
。
在客戶端是不能通過js
程式碼去設定一個httpOnly
型別的cookie
的,這種型別的cookie
只能通過服務端來設定。
那我們在頁面中怎麼知道哪些cookie
是httpOnly
型別的呢?看下圖:
凡是httpOnly
型別的cookie
,其 HTTP 一列都會打上√,如上圖中的PA_VTIME
。你通過document.cookie
是不能獲取的,也不能修改PA_VTIME
的。
——
httpOnly
與安全從上面介紹中,大家是否會有這樣的疑問:為什麼我們要限制客戶端去訪問
cookie
?其實這樣做是為了保障安全。試想:如果任何 cookie 都能被客戶端通過
document.cookie
獲取會發生什麼可怕的事情。當我們的網頁遭受了 XSS 攻擊,有一段惡意的script
指令碼插到了網頁中。這段script
指令碼做的事情是:通過document.cookie
讀取了使用者身份驗證相關的 cookie,並將這些 cookie 傳送到了攻擊者的伺服器。攻擊者輕而易舉就拿到了使用者身份驗證資訊,於是就可以搖搖大擺地冒充此使用者訪問你的伺服器了(因為攻擊者有合法的使用者身份驗證資訊,所以會通過你伺服器的驗證)。
如何設定 cookie?
知道了cookie
的格式,cookie
的屬性選項,接下來我們就可以設定cookie
了。首先得明確一點:cookie
既可以由服務端來設定,也可以由客戶端來設定。
服務端設定 cookie
不管你是請求一個資原始檔(如 html/js/css/圖片),還是傳送一個ajax
請求,服務端都會返回response
。而response header
中有一項叫set-cookie
,是服務端專門用來設定cookie
的。如下圖所示,服務端返回的response header
中有5個set-cookie
欄位,每個欄位對應一個cookie
(注意不能將多個cookie
放在一個set-cookie
欄位中),set-cookie
欄位的值就是普通的字串,每個cookie
還設定了相關屬性選項。
注意:
一個
set-Cookie
欄位只能設定一個cookie
,當你要想設定多個 cookie,需要新增同樣多的set-Cookie
欄位。服務端可以設定cookie 的所有選項:
expires
、domain
、path
、secure
、HttpOnly
客戶端設定 cookie
在網頁即客戶端中我們也可以通過js
程式碼來設定cookie
。如我當前開啟的網址為http://dxw.st.sankuai.com/mp/
,在控制檯中我們執行了下面程式碼:
document.cookie = "name=Jonh; ";
檢視瀏覽器 cookie 皮膚如下圖所示,cookie
確實設定成功了,而且屬性選項 domain
、path
、expires
都用了預設值。
再執行下面程式碼:
document.cookie="age=12; expires=Thu, 26 Feb 2116 11:50:25 GMT; domain=sankuai.com; path=/";
檢視瀏覽器cookie 皮膚,如下圖所示,新的cookie
設定成功了,而且屬性選項 domain
、path
、expires
都變成了設定的值。
注意:
客戶端可以設定cookie 的下列選項:
expires
、domain
、path
、secure
(有條件:只有在https
協議的網頁中,客戶端設定secure
型別的 cookie 才能成功),但無法設定HttpOnly
選項。
用 js 如何設定多個 cookie
當要設定多個cookie
時, js 程式碼很自然地我們會這麼寫:
document.cookie = "name=Jonh; age=12; class=111";
但你會發現這樣寫只是新增了第一個cookie
“name=John”,後面的所有cookie
都沒有新增成功。所以最簡單的設定多個cookie
的方法就在重複執行document.cookie = "key=name"
,如下:
document.cookie = "name=Jonh";
document.cookie = "age=12";
document.cookie = "class=111";
如何修改、刪除
修改 cookie
要想修改一個cookie
,只需要重新賦值就行,舊的值會被新的值覆蓋。但要注意一點,在設定新cookie時,path/domain
這幾個選項一定要舊cookie 保持一樣。否則不會修改舊值,而是新增了一個新的 cookie。
刪除 cookie
刪除一個cookie
也挺簡單,也是重新賦值,只要將這個新cookie的expires
選項設定為一個過去的時間點就行了。但同樣要注意,path/domain/
這幾個選項一定要舊cookie 保持一樣。
cookie 編碼
cookie
其實是個字串,但這個字串中逗號、分號、空格
被當做了特殊符號。所以當cookie的 key 和 value 中含有這3個特殊字元時,需要對其進行額外編碼,一般會用escape
進行編碼,讀取時用unescape
進行解碼;當然也可以用encodeURIComponent/decodeURIComponent
或者encodeURI/decodeURI
(三者的區別可以參考這篇文章)。
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=sankuai.com; path=/";
跨域請求中 cookie
之前在介紹 XHR 的一篇文章裡面提過:預設情況下,在發生跨域時,cookie 作為一種 credential 資訊是不會被傳送到服務端的。必須要進行額外設定才可以。具體原因和如何設定可以參考我的這篇文章:你真的會使用XMLHttpRequest嗎?
另外,關於跨域資源共享 CORS
極力推薦大家閱讀阮一峰老師的這篇 跨域資源共享 CORS 詳解。
其他補充
什麼時候 cookie 會被覆蓋:name/domain/path 這3個欄位都相同的時候;
-
如果顯式設定了 domain,則設定成什麼,瀏覽器就存成什麼;但如果沒有顯式設定,則瀏覽器會自動取 url 的 host 作為 domain 值;
新的規範中,顯式設定 domain 時,如果 value 最前面帶點,則瀏覽器處理時會將這個點去掉,所以最後瀏覽器存的就是沒有點的(注意:但目前大多數瀏覽器並未全部這麼實現)
-
前面帶點‘.’和不帶點‘.’有啥區別:
帶點:任何 subdomain 都可以訪問,包括父 domain
不帶點:只有完全一樣的域名才能訪問,subdomain 不能(但在 IE 下比較特殊,它支援 subdomain 訪問)
總結
我們們今天就聊到這裡,若有不對之處歡迎各位指正~~
最後附上一些參考資料: