關(guān)于cookie的深入了解
1.cookie的誕生
由于HTTP協(xié)議是無(wú)狀態(tài)的,服務(wù)端的業(yè)務(wù)必須帶用戶狀態(tài),cookie的誕生最初就是為了存儲(chǔ)web中的用戶狀態(tài)以及其他的相關(guān)狀態(tài),以方便服務(wù)器使用。比如是否用戶第一次訪問(wèn)網(wǎng)站,用戶是否登錄等。Cookie目前最新的規(guī)范是RFC 6265,它是一個(gè)由瀏覽器和服務(wù)器共同協(xié)作實(shí)現(xiàn)的規(guī)范。
2.cookie的工作原理
Cookie的處理基本步驟是:服務(wù)器向客戶端發(fā)送cookie;瀏覽器保存cookie;瀏覽器再次訪問(wèn)服務(wù)器帶上cookie;可以通過(guò)有效期來(lái)限制cookie的訪問(wèn)時(shí)間,并且cookie不能通過(guò)跨域來(lái)訪問(wèn),還有一些對(duì)象依賴于cookie,比如session。還有就是客戶端cookie數(shù)量限制在300個(gè),不能超過(guò)4kb,每個(gè)web站點(diǎn)設(shè)置的cookie數(shù)量不能超過(guò)20個(gè)。
2.1 服務(wù)器端的發(fā)送與解析
服務(wù)端向客戶端發(fā)送cookie是通過(guò)http響應(yīng)報(bào)文實(shí)現(xiàn)的,在Set-Cookie中設(shè)置需要向客戶端發(fā)送的cookie,格式如下:
Set-Cookie: "name=value;domain=.domain.com;path=/;expires=Sat, 11 Jun 2016 11:29:42 GMT;HttpOnly;secure"
其中name=value是必選項(xiàng),其他都是可選項(xiàng)。Cookie的主要構(gòu)成如下:
- name:一個(gè)唯一確定的cookie名稱(chēng)。通常cookie的名稱(chēng)不區(qū)分大小寫(xiě)。
- value:存儲(chǔ)在cookie中的字符串的值。最好為cookie中的name和value進(jìn)行url編碼。
- domain:cookie對(duì)于那個(gè)域是有效的。客戶端響應(yīng)一次之后,所有向這個(gè)域發(fā)送的請(qǐng)求都會(huì)包含這個(gè)cookie信息。注意這個(gè)值也可以包含子域(例如:如果domain是aliyun.com,則對(duì)阿里云所有的子域都有效,比如,訪問(wèn)yq.aliyun.com,xxx.aliyun.com的請(qǐng)求都會(huì)帶上這個(gè)cookie)。
- path:表示這個(gè)cookie影響到的路徑,瀏覽器會(huì)根據(jù)這個(gè)配置向指定的域中匹配的路徑發(fā)送cookie。
- expires:失效時(shí)間,一個(gè)時(shí)間戳,表示cookie何時(shí)應(yīng)該被刪除,換言之就是何時(shí)瀏覽器停止向服務(wù)器發(fā)送這個(gè)cookie。如果不設(shè)置這個(gè)時(shí)間戳,瀏覽器會(huì)在頁(yè)面關(guān)閉時(shí)刪除所有的cookie。不過(guò)也可以在客戶端自己設(shè)置這個(gè)expires時(shí)間戳。這個(gè)值是GMT時(shí)間格式,如果客戶端和服務(wù)器時(shí)間不一致,使用expires就會(huì)存在偏差。
- max-age:和expires作用相同,用來(lái)告訴瀏覽器此cookie過(guò)多久失效(單位是秒),而不是一個(gè)固定時(shí)間點(diǎn)。正常情況下,max-age的優(yōu)先級(jí)高于expires。
- HttpOnly:告訴瀏覽器不允許通過(guò)腳本document.cookie去更改這個(gè)值,同樣這個(gè)值在document.cookie中也不可見(jiàn)。但是http請(qǐng)求仍然會(huì)攜帶這個(gè)cookie。注意這個(gè)值雖然在JavaScript腳本中不可獲取,但仍然在瀏覽器安裝目錄中以文件形式存在。這個(gè)選項(xiàng)一般在服務(wù)端設(shè)置。
- secure:安全標(biāo)志,指定后,只有在使用SSL鏈接時(shí)候才能發(fā)送到服務(wù)器,http鏈接則不會(huì)傳遞該信息。就算設(shè)置了secure屬性也并不代表其他人不能看到本地保存的cookie信息,所以不要把重要的信息放在cookie中。
使用node.js在服務(wù)端設(shè)置cookie如下:
var http = require('http'); var fs = require('fs'); http.createServer(function(req, res) { res.setHeader('status', '200 OK'); res.setHeader('Set-Cookie', 'isVisit=true;domain=.yourdomain.com;path=/;max-age=1000'); res.write('Hello World'); res.end(); }).listen(8888); console.log('running localhost:8888')
下次請(qǐng)求中攜帶cookie:
直接設(shè)置cookie過(guò)于簡(jiǎn)單粗暴,我們可以包裝成一個(gè)方法:
var serilize = function(name, val, options) { if (!name) { throw new Error("coolie must have name"); } var enc = encodeURIComponent; var parts = []; val = (val !== null && val !== undefined) ? val.toString() : ""; options = options || {}; parts.push(enc(name) + "=" + enc(val)); // domain中必須包含兩個(gè)點(diǎn)號(hào) if (options.domain) { parts.push("domain=" + options.domain); } if (options.path) { parts.push("path=" + options.path); } // 如果不設(shè)置expires和max-age瀏覽器會(huì)在頁(yè)面關(guān)閉時(shí)清空cookie if (options.expires) { parts.push("expires=" + options.expires.toGMTString()); } if (options.maxAge && typeof options.maxAge === "number") { parts.push("max-age=" + options.maxAge); } if (options.httpOnly) { parts.push("HTTPOnly"); } if (options.secure) { parts.push("secure"); } return parts.join(";"); }
需要注意的是,如果給cookie設(shè)置一個(gè)已經(jīng)過(guò)去的cookie,瀏覽器會(huì)立即刪除該cookie;此外domain項(xiàng)不許有兩個(gè)點(diǎn),因此不能設(shè)置為localhost。
2.2 服務(wù)端解析cookie
cookie可以設(shè)置不同的域和路徑,所以對(duì)于同一個(gè)name,value,在不同路徑下是可以重復(fù)的,瀏覽器會(huì)按照與當(dāng)前請(qǐng)求url或頁(yè)面地址最佳匹配的殊勛來(lái)先后排序。
所以當(dāng)前端傳遞到服務(wù)器端的cookie有多個(gè)重復(fù)的name,value時(shí),我們只需要找到最匹配的那個(gè),也就是第一個(gè)。服務(wù)端代碼示例如下:
var parse = function(cstr) { if (!cstr) { return null; } var dec = decodeURIComponent; var cookies = {}; var parts = cstr.split(/\s*;\s*/g); parts.forEach(function(p){ var pos = p.indexOf('='); // name 與value存入cookie之前,必須經(jīng)過(guò)編碼 var name = pos > -1 ? dec(p.substr(0, pos)) : p; var val = pos > -1 ? dec(p.substr(pos + 1)) : null; //只需要拿到最匹配的那個(gè) if (!cookies.hasOwnProperty(name)) { cookies[name] = val; }/* else if (!cookies[name] instanceof Array) { cookies[name] = [cookies[name]].push(val); } else { cookies[name].push(val); }*/ }); return cookies; }
2.3 客戶端的存取
瀏覽器管理服務(wù)器傳遞過(guò)來(lái)的cookie,并允許開(kāi)發(fā)者在JavaScript中使用document.cookie來(lái)存取cookie。但是這個(gè)接口使用起來(lái)不方便,它會(huì)因?yàn)槭褂盟姆绞讲煌憩F(xiàn)出不同的欣慰。
- 當(dāng)用來(lái)回去屬性值時(shí),document.cookie返回當(dāng)前頁(yè)面可用cookie(根據(jù)cookie的域,路徑,失效時(shí)間和安全設(shè)置)的字符串,字符串格式如下:
"name1=value1;name2=value2;name3=value3";
- 當(dāng)用來(lái)設(shè)置值的時(shí)候,document.cookie屬性可寫(xiě),可以設(shè)置為一個(gè)新的cookie字符串。這個(gè)字符串會(huì)被解釋并添加到現(xiàn)有的cookie集合中。如下:
document.cookie = "_fa=aaaffffasdsf;domain=.dojotoolkit.org;path=/"
設(shè)置document.cookie并不會(huì)覆蓋cookie,除非設(shè)置的name,value,domain,path都和一個(gè)已經(jīng)存在的cookie重復(fù)。
由于cookie的讀寫(xiě)很不方便,我們可以自己封裝一些函數(shù)來(lái)處理cookie,主要針對(duì)cookie的添加,修改,刪除操作。
var cookieUtils = { get: function(name){ var cookieName=encodeURIComponent(name) + "="; //只取得最匹配的name,value var cookieStart = document.cookie.indexOf(cookieName); var cookieValue = null; if (cookieStart > -1) { // 從cookieStart算起 var cookieEnd = document.cookie.indexOf(';', cookieStart); //從=后面開(kāi)始 if (cookieEnd > -1) { cookieValue = decodeURIComponent(document.cookie.substring(cookieStart + cookieName.length, cookieEnd)); } else { cookieValue = decodeURIComponent(document.cookie.substring(cookieStart + cookieName.length, document.cookie.length)); } } return cookieValue; }, set: function(name, val, options) { if (!name) { throw new Error("coolie must have name"); } var enc = encodeURIComponent; var parts = []; val = (val !== null && val !== undefined) ? val.toString() : ""; options = options || {}; parts.push(enc(name) + "=" + enc(val)); // domain中必須包含兩個(gè)點(diǎn)號(hào) if (options.domain) { parts.push("domain=" + options.domain); } if (options.path) { parts.push("path=" + options.path); } // 如果不設(shè)置expires和max-age瀏覽器會(huì)在頁(yè)面關(guān)閉時(shí)清空cookie if (options.expires) { parts.push("expires=" + options.expires.toGMTString()); } if (options.maxAge && typeof options.maxAge === "number") { parts.push("max-age=" + options.maxAge); } if (options.httpOnly) { parts.push("HTTPOnly"); } if (options.secure) { parts.push("secure"); } document.cookie = parts.join(";"); }, delete: function(name, options) { options.expires = new Date(0);// 設(shè)置為過(guò)去日期 this.set(name, null, options); } }
當(dāng)然也有一些第三方的js庫(kù),例如:js-cookie
3.HttpOnly
HttpOnly是包含在Set-Cookie Http響應(yīng)頭文件中的附加標(biāo)志。生成cookie時(shí)使用HttpOnly標(biāo)志有助于降低客戶端腳本被訪問(wèn),修改的風(fēng)險(xiǎn)。
如果一個(gè)cookie的選項(xiàng)被設(shè)置成HttpOnly = true的話,此cookie只能通過(guò)服務(wù)器修改,js操作不來(lái)。如下:

作者:Tyler Ning
出處:http://www.rzrgm.cn/tylerdonet/
本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁(yè)面明顯位置給出原文連接,如有問(wèn)題,請(qǐng)微信聯(lián)系冬天里的一把火
浙公網(wǎng)安備 33010602011771號(hào)