因爲 http 是一種無狀態的協議(無狀態是指對於客戶端每次發送的請求都認爲它是一個新的請求,上一次會話和下一次會話沒有聯繫),服務器沒法知道兩個請求是否來自於同一個瀏覽器,所以 cookie 應運而生。cookie 能夠記錄用戶的有關信息,最根本的是它能夠幫助 Web 站點保存有關訪問者的信息。javascript
咱們經過一張圖來講明下 cookie 的原理:html
客戶端向服務端發送請求;接收到請求後,服務端在響應頭中經過 set-cookie 攜帶 cookie 信息返回給客戶端;客戶端再次發送攜帶了 cookie 的請求;服務端根據 request header 裏的 cookie 信息校驗該請求,給出響應。前端
接下來咱們須要先簡要了解下 cookie 的屬性 [1][2]。java
1. Nameios
cookie 的名字,相同域名只容許存在一個同名 cookie;一旦建立,該名稱便不可更改。程序員
document.cookie = `jxi-m-sid=${cookie}`;
jxi-m-sid 即爲新建的 cookie 名字,該名字建立後不能修改,若是須要新的 cookie 只能再次建立。web
2. Value算法
該 cookie 的值,若是值爲 Unicode 字符,須要爲字符編碼;若是值爲二進制數據,則須要使用 BASE64 編碼。axios
document.cookie = `jxi-m-sid=${cookie}`;
${cookie} 即爲 cookie jxi-m-sid 的值,當須要修改值時,好比將 jxi-m-sid 值改成 2019,操做以下:小程序
document.cookie = `jxi-m-sid=2019`;
3. Domain
能夠訪問該 cookie 的域名。默認狀況下,domain 會被設置爲建立該 cookie 的頁面所在的域名,因此當給相同域名發送請求時該 cookie 會被髮送至服務器。
有關 domain 的設置須要注意如下幾點:
(1)設置 domain 時,前面帶點和不帶點的區別是:
帶點:任何子域名均可以訪問,包括父域名;
document.cookie = `jxi-m-sid=${cookie};domain=.xx.com;path=/`;
不帶點:只有徹底同樣的域名才能夠訪問(IE除外,仍然支持子域名訪問)
document.cookie = `jxi-m-sid=${cookie};domain=xx.com;path=/`;
(2)非頂級域名,如二級域名或者三級域名,設置的 cookie 的 domain 只能爲頂級域名或者二級域名或者三級域名自己,不能設置其餘二級域名的 cookie ,不然 cookie 沒法生成;
(3)二級域名能讀取設置了 domain 爲頂級域名或者自身的 cookie,不能讀取其餘二級域名 domain 的 cookie 。因此要想 cookie 在多個二級域名中共享,須要設置 domain 爲頂級域名;
(4)頂級域名只能獲取到 domain 設置爲頂級域名的 cookie 。
4. Path
能夠訪問該 cookie 的頁面路徑。好比 domain 是 abc.com ,path是 /test ,那麼只有 /test 路徑下的頁面能夠讀取該 cookie。
document.cookie = `jxi-m-sid=${cookie};domain=.abc.com;path=/test`;
5. Expires/Max-Age
該 cookie 的超時時間。若設置其值爲一個時間,當到達此時間後,該 cookie 失效。默認有效期爲 session ,即會話 cookie 。當瀏覽器關閉(不是瀏覽器標籤頁,而是整個瀏覽器) 後,該 cookie 失效。
例如,咱們但願該 cookie jxi-m-sid 過時,則能夠將它的超時時間設置爲客戶端本地時間1分鐘之前,代碼以下:
var exp = new Date(); //獲取客戶端本地當前系統時間 exp.setTime(exp.getTime() - 60 * 1000); //將 exp 設置爲客戶端本地時間1分鐘之前 document.cookie = `jxi-m-sid=;expires=${exp.toUTCString()};domain=.xx.com;path=/`;
注意:expires 必須是 GMT 格式的時間。
6. Size
該 cookie 的大小。cookie 的大小約 4K 左右,在全部瀏覽器中,任何 cookie 大小超過限制都會被忽略,且永遠不會被設置。
7. HTTP
cookie 的 httponly 屬性。默認狀況下,httpOnly 選項爲空,容許客戶端經過 js 去訪問(包括讀取、修改、刪除等)該 cookie ;若此屬性爲 true ,則只有在 http 請求頭中會帶有此 cookie 的信息,而不能經過 document.cookie 來訪問此 cookie,意在提供一個安全措施來幫助阻止經過 JavaScript 發起的跨站腳本攻擊 (XSS) 竊取 cookie 的行爲。
好比,服務端將 uid 設置爲不容許客戶端修改:
response.addHeader("Set-Cookie", "uid=112; Path=/; HttpOnly");
8. Secure
用來設置 cookie 只在確保安全的請求中才會發送。當請求是 https 或者其餘安全協議時,包含 secure 選項的 cookie 才能被髮送至服務器。默認狀況下,secure 選項爲空,不論是 https 協議仍是 http 協議的請求,cookie 都會被髮送至服務端。若想在客戶端經過 js 去設置 secure 類型的 cookie,必須保證網頁是 https 協議的。好比:
document.cookie = `jxi-m-sid=${cookie};secure`;
cookie 的屬性介紹完了,關於其使用須要注意:
(1)每一個 web 服務器(域名)保存的 cookie 數不能超過 50 個,每一個 cookie 大小不能超過 4KB;
(2)儘可能讓 cookie 的權限範圍小一些,能子域可見 domain 毫不設爲主域;
(3)存儲非敏感的用戶信息且設置合理的過時時間,減小所以帶來的網絡流量(文檔傳輸的負載)。
cookie 能夠分爲兩類 [3]:會話 cookie 和持久 cookie。
會話 cookie 是一種臨時 cookie,若沒有設置有效期,當用戶關閉瀏覽器時,該 cookie 就會被刪除。
對於設置了有效期的 cookie 就被稱爲持久 cookie,它能夠存儲在硬盤上,當用戶關閉瀏覽器或者機器重啓時,該 cookie 依然存在,能夠再次被讀取使用。
在經常使用的數據存儲方法中,除了本文介紹的 cookie,還有 localStorage、sessionStorage、 session。下面簡要介紹下這幾種方法的區別:
session 機制是一種服務器端的機制,服務器使用一種相似於散列表的結構(也可能就是使用散列表)來保存信息。大多數的應用都是用 cookie 來實現 session 的跟蹤,第一次建立 session 的時候,服務端會在 http 協議中告訴客戶端,須要在 cookie 裏面記錄一個 sessionid ,之後每次請求把這個會話 id 發送到服務器。
cookie 與 session 的區別 [4] 以下表所示:
特性 | cookie | session |
存取方式 | 只能存儲 ASCII 字符串 | 存取任何類型的數據 |
隱私策略 | 存儲在客戶端,對用戶可見且用戶可對其處理 | 存儲在服務器 |
有效期 | 能夠經過設置較大的過時時間實現長期有效 | 若設置的超時時間過長,容易致使內存溢出 |
服務器壓力 | 不佔用服務器資源 | 併發用戶過多時會耗費大量內存 |
瀏覽器支持 | 須要瀏覽器支持,不支持時能夠經過 session 和 URL 地址重寫實現 | 只在當前窗口和其子窗口內有效 |
跨域 | 支持 | 不支持 |
關於 cookie 與 localStorage、sessionStorage 的區別也整理了一份圖表以下所示:
特性 | cookie | localStorage | sessionStorage |
生命週期 | 通常由服務器生成,可設置失效時間。若是是瀏覽器端生成,默認關閉瀏覽器後失效 | 除非被清除,不然永久保存 | 僅在當前會話下有效,關閉頁面或瀏覽器後被清除 |
存放數據大小 | 4K 左右 | 通常爲 5MB | 同 localStorage |
與服務器端通訊 | 始終攜帶在 http 頭中,使用過多會帶來性能問題 | 僅在客戶端中保存,不和服務器的通訊 | 同 localStorage |
易用性 | 須要程序員本身封裝,源生的 cookie 接口不友好 | 源生接口能夠接受,也可再次封裝 | 同 localStorage |
須要注意的是,向 cookie、localStorage 和 sessionStorage 中存儲數據時,都須要時刻注意是否有代碼存在 XSS 注入的風險,避免存入一些敏感數據。
重點來了:結合項目實踐中的問題討論下 cookie 跨域存儲的問題。
項目背景:在 a 小程序中經過 web-view 內嵌了 b 項目(H5,域名爲 b.mm.com)和 c 項目(H5,域名爲 c.mm.com),b 和 c 都調用了公共地址接口 interface(interface 所在的域名爲 c.mm.com)。在 a 中登陸成功以後,須要獲取接口返回的 cookie 封裝在每一個接口的 request header 中,同時須要傳遞給 b、c;b 和 c 須要獲取傳遞的 cookie 以後,寫入到 interface 可訪問的域名下,實現用戶登陸狀態的驗證。
遇到的問題:小程序內訪問線上項目 c 能夠正常使用,訪問 b 時會偶爾出現登陸驗證失敗的狀況。
服務端反饋傳入的 cookie 值有誤,致使登陸驗證不經過。瀏覽器中查看 cookie 發現:存在兩個同名但 domain 不一樣的 cookie。
問題定位:項目 b、c 獲取 cookie 的方法是同樣的,區別就在於 cookie 值的存儲與傳遞。
項目 b 中 cookie 處理流程:
(1)服務端:b 系統的 domain 爲 mm.com;
//application.yml 文件 ... b: m: domain: mm.com scheme: http ...
(2)前端:獲取當前傳入的 cookie,寫入 mm.com 域名下。
在項目 b 中,b.mm.com 域名下是沒法向 c.mm.com 域名下寫入 cookie,若要實現跨域寫入,只能將 cookie 寫入到其父級域名:mm.com。
document.cookie = `jxi-m-sid=${cookie};domain=.mm.com;path=/`;
注意:b 系統中的 domain 設置爲 mm.com,主要是爲了請求 c.mm.com 下的地址接口時 request header 中攜帶存入的 cookie 。
項目 c 中 cookie 處理流程:
(1)服務端:c 系統的 domain 爲 c.mm.com;
//application.yml 文件 ... c: m: domain: c.mm.com scheme: http ...
(2)前端:獲取當前傳入的 cookie,寫入的域名是 c.mm.com。
document.cookie = `jxi-m-sid=${cookie};domain=c.mm.com;path=/`;
由系統 b 和 c 中 cookie 處理流程能夠發現:寫入 cookie 時設置的 domain 不一樣,致使出現了兩個 jxi-m-sid 的 cookie,當 b 中請求地址接口時,request header 中攜帶的是錯誤的 cookie。
解決方法:爲了解決同時出現兩個同 key 的 cookie 問題,將 c 中的 domain 統一修改成二級域名:mm.com。與此同時須要注意:服務器端須要將 c 系統的 domain 也改成 mm.com。
... c: m: domain: mm.com scheme: http ...
document.cookie = `jxi-m-sid=${cookie};domain=.mm.com;path=/`;
這樣處理以後,兩個同 key cookie 的問題解決了,爲了保證寫入的 cookie 是惟一的,在每次寫入 cookie 以前作了清除同 key cookie 處理(利用 cookie 的超時時間屬性)。
removeCookie: (cookieName) => { var cookies = document.cookie.split(";"); for (var i = 0; i < cookies.length; i++) { if (cookies[i].indexOf(" ") == 0) { cookies[i] = cookies[i].substring(1); } if (cookies[i].indexOf(cookieName) == 0) { var exp = new Date(); exp.setTime(exp.getTime() - 60 * 1000); document.cookie = cookies[i] + "=;expires=" + exp.toUTCString() + ";domain=.xx.com;path=/" break; } } } removeCookie('jxi-m-sid');
按照這種方法處理以後,很長一段時間內 cookie 的問題正常了,可是極少狀況下仍是會偶現服務端取到的 cookie 值不是最新的問題。
終極方案:咱們想出了另一種方案:在 b 和 c 系統全部請求的 request header 中攜帶 cookie,服務端校驗用戶身份時,首先會從 request header 中獲取,沒有的話再從寫入的 cookie 中取值,解決了上述先後端 cookie 值不一致問題。
接下來咱們將項目開發中整個 cookie 處理過程分爲如下四步,簡要說下每一步的處理過程:
登陸成功時,後端會將 cookie 種在 response header 的 set-cookie 中,咱們獲取到該 cookie 後先進行本地存儲,而後封裝到每次接口的 request header 中。
//獲取 cookie wx.setStorageSync('cookie', res.header["Set-Cookie"]); //統一封裝 request 請求 const request = parameter => { //url必填項 if (!parameter || parameter == {} || !parameter.url) { console.log('Data request can not be executed without URL.'); return false; } else { var murl = parameter.url; var headerCookie = wx.getStorageSync('cookie'); //判斷是否有獨自cookie請求 var selfCookie = parameter.selfCookie; selfCookie && (headerCookie += selfCookie); wx.request({ url: murl, data: parameter.data || {}, header: { 'Cookie': headerCookie }, method: parameter.method || 'POST', success: function(res) { parameter.success && parameter.success(res); }, fail: function(e) { parameter.fail && parameter.fail(e); }, complete: function() { parameter.complete && parameter.complete(); } }); } }
這樣在小程序內,每一個接口的 request header 中都會攜帶該 cookie,服務端能夠根據該 cookie 判斷用戶的登陸狀態。
獲取 cookie:
getQueryString: (name) => { let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); let r = window.location.search.substr(1).match(reg); if (r != null) { return unescape(decodeURI(r[2])); return null; } } let cookie = getQueryString('cookie');
經過 js 將 cookie 寫入到父級域名:mm.com。寫入方法以下:
document.cookie = `jxi-m-sid=${cookie};domain=.mm.com;path=/`;
export default { post(url, data) { return axios({ method: 'post', url, data: JSON.stringify(data), timeout: 30000, headers: { 'jxi-m-sid': cookie } }) }, get(url, params) { return axios({ method: 'get', url, params, timeout: 30000, headers: { 'jxi-m-sid': cookie } }) } }
以上就是基於咱們的項目背景,解決兩個同 key cookie 且先後端獲取值不一致問題的解決方案。涉及到 cookie 的 domain、path 問題請你們使用時高度重視,這將決定你所寫入 cookie 的惟一性。
cookie 通常是用來存儲當前登陸用戶的會話信息且是存儲在客戶端,用戶能夠隨意修改,因此存在必定的風險。針對這個問題,也有了比較成熟的解決方法,這裏咱們簡要介紹下。
敏感數據避免存儲在 cookie 裏,能夠根據 sessionid 將其存儲在服務端。須要時根據 sessionid 獲取便可。
服務端爲每一個 cookie 生成簽名,若是用戶篡改了該 cookie 則簽名是不一致的,服務端能夠以此來判斷該 cookie 是否被篡改。
具體的實現步驟能夠以下所示:
(1)服務端提供簽名生成算法 secret;
(2)根據方法生成簽名 secret(x);
(3)將生成的簽名放到 cookie 中,可使用 | 將 cookie 內容與簽名分隔開,如 name=x|yyyyy;
(4)服務端校驗收到的內容和簽名,判斷是否被篡改。
以上的方法能夠進一步確保 cookie 的數據安全 [6],在有須要的項目中你們能夠嘗試使用下。
本文主要介紹了 cookie 的相關知識,包括:經常使用屬性、修改、跨域存儲、防篡改等。對於 cookie 的跨域傳遞,上述項目實踐中的方法是咱們的使用方法,你們在使用的時候能夠根據本身項目的狀況進行選擇。
敲黑板:cookie 中不要放敏感信息哦,再次友情提示~好了,關於 cookie 的問題就介紹到這裏了,若有任何疑問,歡迎留言。
[1] https://www.quirksmode.org/js/cookies.html
[2] http://bubkoo.com/2014/04/21/http-cookies-explained/
[3] http://www.allaboutcookies.org/cookies/cookies-the-same.html
[4] https://www.jianshu.com/p/25802021be63
[5] http://www.tutorialspoint.com/javascript/javascript_cookies.htm