在瀏覽器端,雖然以LocalStorage為代表的本地持久化方案已經非常成熟,但cookie因其可跨三級及以上域名、可後端參與操作等特性,在涉及使用者驗證等諸多業務中依然被廣泛運用。
Cookie規格
Cookie的基本組成主要由4部分組成:
- Key => Value 儲存資料
- Path 作用路徑
- Domain 作用域
- Expires 過期時間,或者叫生命週期
我們可以用谷歌控制檯體會一下。
開啟suning.com
,然後在控制檯輸入以下程式碼:
document.cookie = `my_data=123; expires=${new Date(2021,0,1).toGMTString()};`
複製程式碼
然後進入Application
->Cookie
->https://www.suning.com
,可以看到my_data
已經被寫入列表。
在上面的例子中,我們並沒有指定Path
和Domain
兩個值,所以瀏覽器給出了預設值:
Path=/
作用於根及以下分支目錄Domain=www.suning.com
作用於該域
如果希望Cookie在三級及以上域名作用,可簡單加一條:
document.cookie = `my_data=123; domain=suning.com; expires=${new Date(2021,0,1).toGMTString()};`
複製程式碼
可以看到,Cookie中增加了一條my_data
記錄domain=.suning.com
。這裡的.suning.com
可以理解為*.suning.com
。
關於Path
項及其他Cookie項,這裡就不多做解釋了,大家可以自行嘗試或者查詢資料,在控制檯裡盡情虐待瀏覽器的cookie。
這裡有幾個小的經驗追加一下:
- 日期格式非常鬆散。GMT時間格式是Cookie的標準時間格式,但其他時間格式谷歌瀏覽器也可以識別,這個方面大家可以自己探索一下,就不贅述了。因為單純是日期格式問題,就可以寫好多字。
- Expires時間是UTC時間。
- 瀏覽器的
document.cookie
雖然是以字串形式賦值與取值,但其背後的管理機制卻是類似Map型別。 - Cookie的銷燬,是通過設定expires為一個過去時,但需要注意其
domain
path
等需要對應。比如:
document.cookie = `my_data=123; domain=suning.com; expires=${new Date(0).toGMTString()};`
複製程式碼
後端Set-Cookie
後端往前端塞cookie是一種常用操作,方法是在響應頭中設定set-cookie
。瀏覽器會接收這個頭部,並原樣設定到本地cookie中。
Set-Cookie: <name>=<value>[; <name>=<value>]...
[; expires=<date>]
[; domain=<domain_name>]
[; path=<some_path>]
[; secure]
[; httponly]
複製程式碼
這個格式看起來是不是與剛才設定document.cookie一致?補充最後兩項說明:
secure
表示cookie只能被髮送到http伺服器。httponly
表示cookie不能被客戶端指令碼獲取到。
關於HTTP響應頭,這裡提兩個注意事項:
- 頭部的KEY可以重複多個。也就是說,頭部是有可能出現多個
set-cookie
的。之前我曾遇到這樣一個案例:後端工程師使用NodeJS+KOA做服務,對允許跨域的請求設定了頭部Access-Control-Allow-Origin: *
。當服務使用Docker部署後,運維的同學對前置的Nginx
也配置了Access-Control-Allow-Origin
,所以前端拿到的響應頭中出現兩個Access-Control-Allow-Origin
。結果瀏覽器判定此項頭配置無效,造成跨域請求失敗。當然,set-cookie
出現多個沒有問題,只是此種情況需要做適配處理。 - 頭部的KEY大小寫不敏感。但這不代表處理頭部的應用會大小寫不敏感。
Cookie與encodeURI
關於URI/URL/URN
- URI: Uniform Resource Identifier
- URL: Uniform Resource Locator
- URN: Uniform Resource Name
在進行web訪問時,一個web地址(URL)由protocol+domain+path組成。
- protocol: http/https/ftp 等等
- domain: 類似
www.suning.com
- path:
/aaa/b/cc/d.html
醬嬸兒的 這樣就形成了對一個網路資源的有效定位,也就是一個Locator
。而這個URL實際上在全網範圍也是一個唯一標識,所以也是一個URI。簡單點說,URL是URI的一種。
URN我沒有遇到(意識到?)典型的場景,按RFC來講同樣是URI的一個子集。
關於encodeURI與encodeURIComponent
這兩者的區別在於對URI轉義的字符集不同。encodeURI
並不會對一個URI的分割標記做出轉移,比如://
啦#
啦()
啦?&=
...,等等。
對encodeURIComponent
望文生義的話,是對URI部分的轉義,意圖是被轉義的部分不影響URL的解析,那麼就必須將其中的URI定義的字元全部轉義。例子:
window.location.href = `https://auth.suning.com?redir=${encodeURIComponent('https://www.suning.com')}`;
// https://auth.suning.com?redir=https%3A%2F%2Fwww.suning.com
window.location.href = `${encodeURI('https://www.suning.com/prod.do?location=北京')}`;
// https://www.suning.com/prod.do?location=%E5%8C%97%E4%BA%AC
複製程式碼
最終的目的,是在不破壞URI的前提下,完整傳遞引數。
encodeURI在Cookie操作中的應用
我們獲取cookie的途徑,不僅是響應頭的set-cookie
。也許是localStorage資料,也許是一次API請求。
由於HTTP規範中,頭中只允許使用ASCII字元,所以在向請求頭塞cookie的時候,需要對cookie來源的字串做預處理。這時候,就用到了encodeURI
/encodeURIComponent
。由於在GMT時間格式中存在URI的敏感字,也就是說,被encodeURI的忽略的字符集,小於header允許的字符集。所以一次性encode並不可取,需在cookie拼接完成後,做全量分析和處理。
Cookie字串的切割與分析
如文章開頭所說,為了相容、複用一些老的登入框架,往往需要用到cookie處理機制。由於前端AJAX無法處理302請求問題,也就無法處理寫cookie跳轉的登入行為,所以你可能需要一個後端介面去代理一系列的後端set-cookie行為。
假設中間我們跳轉了兩個頁面,每個頁面set-cookie一次,那麼在代理頁面返回的響應頭中,會至少包含2條set-cookie
頭部。出於安全考慮,XMLHttpRequest
並不能拿到頭部的set-cookie
,所以接下來,我們用小程式中的request方法,來作為進一步演示cookie處理問題的工具。
(依然未完)