新聞中心
我發(fā)現(xiàn)網(wǎng)上很多文章對(duì) token 的介紹有誤,所以對(duì) cookie,session,token 作了一下對(duì)比(文中 token 指 jwt token)相信大家看完肯定有收獲!

為甌海等地區(qū)用戶提供了全套網(wǎng)頁(yè)設(shè)計(jì)制作服務(wù),及甌海網(wǎng)站建設(shè)行業(yè)解決方案。主營(yíng)業(yè)務(wù)為網(wǎng)站制作、網(wǎng)站設(shè)計(jì)、甌海網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠(chéng)的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(rèn)可,從而選擇與我們長(zhǎng)期合作。這樣,我們也可以走得更遠(yuǎn)!
Cookie
1991 年 HTTP 0.9 誕生了,當(dāng)時(shí)只是為了滿足大家瀏覽 Web 文檔的要求 ,所以只有 GET 請(qǐng)求,瀏覽完了就走了,兩個(gè)連接之間是沒有任何聯(lián)系的,這也是 HTTP 為無狀態(tài)的原因,因?yàn)樗Q生之初就沒有這個(gè)需求。
但隨著交互式 Web 的興起(所謂交互式就是你不光可以瀏覽,還可以登錄,發(fā)評(píng)論,購(gòu)物等用戶操作的行為),單純地瀏覽 Web 已經(jīng)無法滿足人們的要求。
比如隨著網(wǎng)上購(gòu)物的興起,需要記錄用戶的購(gòu)物車記錄,就需要有一個(gè)機(jī)制記錄每個(gè)連接的關(guān)系,這樣我們就知道加入購(gòu)物車的商品到底屬于誰了,于是 Cookie 就誕生了。
Cookie,有時(shí)也用其復(fù)數(shù)形式 Cookies。類型為“小型文本文件”,是某些網(wǎng)站為了辨別用戶身份,進(jìn)行 Session 跟蹤而儲(chǔ)存在用戶本地終端上的數(shù)據(jù)(通常經(jīng)過加密),由用戶客戶端計(jì)算機(jī)暫時(shí)或永久保存的信息 。
工作機(jī)制如下:
以加入購(gòu)物車為例,每次瀏覽器請(qǐng)求后 server 都會(huì)將本次商品 id 存儲(chǔ)在 Cookie 中返回給客戶端,客戶端會(huì)將 Cookie 保存在本地,下一次再將上次保存在本地的 Cookie 傳給 server 就行了。
這樣每個(gè) Cookie 都保存著用戶的商品 id,購(gòu)買記錄也就不會(huì)丟失了。
仔細(xì)觀察上圖相信你不難發(fā)現(xiàn)隨著購(gòu)物車內(nèi)的商品越來越多,每次請(qǐng)求的 cookie 也越來越大,這對(duì)每個(gè)請(qǐng)求來說是一個(gè)很大的負(fù)擔(dān)。
我只是想將一個(gè)商品加入購(gòu)買車,為何要將歷史的商品記錄也一起返回給 server?購(gòu)物車信息其實(shí)已經(jīng)記錄在 server 了,瀏覽器這樣的操作豈不是多此一舉?怎么改進(jìn)呢?
Session
仔細(xì)考慮下,由于用戶的購(gòu)物車信息都會(huì)保存在 Server 中,所以在 Cookie 里只要保存能識(shí)別用戶身份的信息,知道是誰發(fā)起了加入購(gòu)物車操作即可。
這樣每次請(qǐng)求后只要在 Cookie 里帶上用戶的身份信息,請(qǐng)求體里也只要帶上本次加入購(gòu)物車的商品 id,大大減少了 cookie 的體積大小。
我們把這種能識(shí)別哪個(gè)請(qǐng)求由哪個(gè)用戶發(fā)起的機(jī)制稱為 Session(會(huì)話機(jī)制),生成的能識(shí)別用戶身份信息的字符串稱為 sessionId。
它的工作機(jī)制如下:
- 首先用戶登錄,server 會(huì)為用戶生成一個(gè) session,為其分配唯一的 sessionId,這個(gè) sessionId 是與某個(gè)用戶綁定的。
也就是說根據(jù)此 sessionid(假設(shè)為 abc) 可以查詢到它到底是哪個(gè)用戶,然后將此 sessionid 通過 cookie 傳給瀏覽器。
- 之后瀏覽器的每次添加購(gòu)物車請(qǐng)求中只要在 cookie 里帶上 sessionId=abc 這一個(gè)鍵值對(duì)即可,server 根據(jù) sessionId 找到它對(duì)應(yīng)的用戶后,把傳過來的商品 id 保存到 server 中對(duì)應(yīng)用戶的購(gòu)物車即可。
可以看到通過這種方式再也不需要在 cookie 里傳所有的購(gòu)物車的商品 id 了,大大減輕了請(qǐng)求的負(fù)擔(dān)!
另外通過上文不難觀察出 cookie 是存儲(chǔ)在 client 的,而 session 保存在 server,sessionId 需要借助 cookie 的傳遞才有意義。
Session 的痛點(diǎn)
看起來通過 cookie+session 的方式是解決了問題, 但是我們忽略了一個(gè)問題,上述情況能正常工作是因?yàn)槲覀兗僭O(shè) server 是單機(jī)工作的。
但實(shí)際在生產(chǎn)上,為了保障高可用,一般服務(wù)器至少需要兩臺(tái)機(jī)器,通過負(fù)載均衡的方式來決定到底請(qǐng)求該打到哪臺(tái)機(jī)器上。
如圖示:客戶端請(qǐng)求后,由負(fù)載均衡器(如 Nginx)來決定到底打到哪臺(tái)機(jī)器。
假設(shè)登錄請(qǐng)求打到了 A 機(jī)器,A 機(jī)器生成了 session 并在 cookie 里添加 sessionId 返回給了瀏覽器。
那么問題來了:下次添加購(gòu)物車時(shí)如果請(qǐng)求打到了 B 或者 C,由于 session 是在 A 機(jī)器生成的,此時(shí)的 B,C 是找不到 session 的,那么就會(huì)發(fā)生無法添加購(gòu)物車的錯(cuò)誤,就得重新登錄了,此時(shí)請(qǐng)問該怎么辦?
主要有以下三種方式:
①session 復(fù)制
A 生成 session 后復(fù)制到 B, C,這樣每臺(tái)機(jī)器都有一份 session,無論添加購(gòu)物車的請(qǐng)求打到哪臺(tái)機(jī)器,由于 session 都能找到,故不會(huì)有問題。
這種方式雖然可行,但缺點(diǎn)也很明顯:
- 同一樣的一份 session 保存了多份,數(shù)據(jù)冗余。
- 如果節(jié)點(diǎn)少還好,但如果節(jié)點(diǎn)多的話,特別是像阿里,微信這種由于 DAU 上億,可能需要部署成千上萬臺(tái)機(jī)器,這樣節(jié)點(diǎn)增多復(fù)制造成的性能消耗也會(huì)很大。
②session 粘連
這種方式是讓每個(gè)客戶端請(qǐng)求只打到固定的一臺(tái)機(jī)器上,比如瀏覽器登錄請(qǐng)求打到 A 機(jī)器后,后續(xù)所有的添加購(gòu)物車請(qǐng)求也都打到 A 機(jī)器上,Nginx 的 sticky 模塊可以支持這種方式,支持按 ip 或 cookie 粘連等等。
如按 ip 粘連方式如下:
- upstream tomcats {
- ip_hash;
- server 10.1.1.107:88;
- server 10.1.1.132:80;
- }
這樣的話每個(gè) client 請(qǐng)求到達(dá) Nginx 后,只要它的 ip 不變,根據(jù) ip hash 算出來的值會(huì)打到固定的機(jī)器上,也就不存在 session 找不到的問題了,當(dāng)然不難看出這種方式缺點(diǎn)也是很明顯,對(duì)應(yīng)的機(jī)器掛了怎么辦?
③session 共享
這種方式也是目前各大公司普遍采用的方案,將 session 保存在 redis,memcached 等中間件中,請(qǐng)求到來時(shí),各個(gè)機(jī)器去這些中間件取一下 session 即可。
缺點(diǎn)其實(shí)也不難發(fā)現(xiàn),就是每個(gè)請(qǐng)求都要去 redis 取一下 session,多了一次內(nèi)部連接,消耗了一點(diǎn)性能。
另外為了保證 redis 的高可用,必須做集群,當(dāng)然了對(duì)于大公司來說,redis 集群基本都會(huì)部署,所以這方案可以說是大公司的選擇了。
Token:no session!
通過上文分析我們知道通過在服務(wù)端共享 session 的方式可以完成用戶的身份定位,但是不難發(fā)現(xiàn)也有一個(gè)小小的瑕疵:搞個(gè)校驗(yàn)機(jī)制我還得搭個(gè) redis 集群?
大廠確實(shí) redis 用得比較普遍,但對(duì)于小廠來說可能它的業(yè)務(wù)量還未達(dá)到用 redis 的程度,所以有沒有其他不用 server 存儲(chǔ) session 的用戶身份校驗(yàn)機(jī)制呢,這就是我們今天要介紹的主角:token。
首先請(qǐng)求方輸入自己的用戶名,密碼,然后 server 據(jù)此生成 token,客戶端拿到 token 后會(huì)保存到本地,之后向 server 請(qǐng)求時(shí)在請(qǐng)求頭帶上此 token 即可。
相信大家看了上圖會(huì)發(fā)現(xiàn)存在兩個(gè)問題:
①token 只存儲(chǔ)在瀏覽器中,服務(wù)端卻沒有存儲(chǔ),這樣的話我隨便搞個(gè) token 傳給 server 也行?
答:server 會(huì)有一套校驗(yàn)機(jī)制,校驗(yàn)這個(gè) token 是否合法。
②怎么不像 session 那樣根據(jù) sessionId 找到 userid 呢,這樣的話怎么知道是哪個(gè)用戶?
答:token 本身攜帶 uid 信息。
第一個(gè)問題,如何校驗(yàn) token 呢?我們可以借鑒 HTTPS 的簽名機(jī)制來校驗(yàn)。
先來看 jwt token 的組成部分:
可以看到 token 主要由三部分組成:
- header:指定了簽名算法。
- payload:可以指定用戶 id,過期時(shí)間等非敏感數(shù)據(jù)。
- Signature:簽名,server 根據(jù) header 知道它該用哪種簽名算法,再用密鑰根據(jù)此簽名算法對(duì) head+payload 生成簽名,這樣一個(gè) token 就生成了。
當(dāng) server 收到瀏覽器傳過來的 token 時(shí),它會(huì)首先取出 token 中的 header+payload,根據(jù)密鑰生成簽名,然后再與 token 中的簽名比對(duì),如果成功則說明簽名是合法的,即 token 是合法的。
而且你會(huì)發(fā)現(xiàn) payload 中存有我們的 userId,所以拿到 token 后直接在 payload 中就可獲取 userid,避免了像 session 那樣要從 redis 去取的開銷。
畫外音:header,payload 實(shí)際上是以 base64 的形式存在的,文中為了描述方便,省去了這一步。
你會(huì)發(fā)現(xiàn)這種方式確實(shí)很妙,只要 server 保證密鑰不泄露,那么生成的 token 就是安全的,因?yàn)槿绻麄卧?token 的話在簽名驗(yàn)證環(huán)節(jié)是無法通過的,就此即可判定 token 非法。
可以看到通過這種方式有效地避免了 token 必須保存在 server 的弊端,實(shí)現(xiàn)了分布式存儲(chǔ)。
不過需要注意的是,token 一旦由 server 生成,它就是有效的,直到過期,無法讓 token 失效,除非在 server 為 token 設(shè)立一個(gè)黑名單。
在校驗(yàn) token 前先過一遍此黑名單,如果在黑名單里則此 token 失效,但一旦這樣做的話,那就意味著黑名單就必須保存在 server,這又回到了 session 的模式,那直接用 session 不香嗎?
所以一般的做法是當(dāng)客戶端登出要讓 token 失效時(shí),直接在本地移除 token 即可,下次登錄重新生成 token 就好。
另外需要注意的是 token 一般是放在 header 的 Authorization 自定義頭里,不是放在 Cookie 里的,這主要是為了解決跨域不能共享 Cookie 的問題 (下文詳述)。
Cookie 與 Token 的簡(jiǎn)單總結(jié)
Cookie 有哪些局限性?
①Cookie 跨站是不能共享的,這樣的話如果你要實(shí)現(xiàn)多應(yīng)用(多系統(tǒng))的單點(diǎn)登錄(SSO),使用 Cookie 來做需要的話就很困難了(要用比較復(fù)雜的 trick 來實(shí)現(xiàn))。
畫外音:所謂單點(diǎn)登錄,是指在多個(gè)應(yīng)用系統(tǒng)中,用戶只需要登錄一次就可以訪問所有相互信任的應(yīng)用系統(tǒng)。
但如果用 token 來實(shí)現(xiàn) SSO 會(huì)非常簡(jiǎn)單,如下:
只要在 header 中的 authorize 字段(或其他自定義)加上 token 即可完成所有跨域站點(diǎn)的認(rèn)證。
②在移動(dòng)端原生請(qǐng)求是沒有 cookie 之說的,而 sessionid 依賴于 cookie,sessionid 就不能用 cookie 來傳了。
如果用 token 的話,由于它是隨著 header 的 authoriize 傳過來的,也就不存在此問題,換句話說token 天生支持移動(dòng)平臺(tái),可擴(kuò)展性好。
綜上所述,token 具有存儲(chǔ)實(shí)現(xiàn)簡(jiǎn)單,擴(kuò)展性好這些特點(diǎn)。
token 有哪些缺點(diǎn)?
那有人就問了,既然 token 這么好,那為什么各個(gè)大公司幾乎都采用共享 session 的方式呢,可能很多人是第一次聽到 token,token 不香嗎?
token 有以下兩點(diǎn)劣勢(shì):
①token 太長(zhǎng)了
token 是 header,payload 編碼后的樣式,所以一般要比 sessionId 長(zhǎng)很多,很有可能超出 cookie 的大小限制(cookie 一般有大小限制的,如 4kb)。
如果你在 token 中存儲(chǔ)的信息越長(zhǎng),那么 token 本身也會(huì)越長(zhǎng),這樣的話由于你每次請(qǐng)求都會(huì)帶上 token,對(duì)請(qǐng)求來是個(gè)不小的負(fù)擔(dān)。
②不太安全
網(wǎng)上很多文章說 token 更安全,其實(shí)不然,細(xì)心的你可能發(fā)現(xiàn)了,我們說 token 是存在瀏覽器的,再細(xì)問,存在瀏覽器的哪里?
既然它太長(zhǎng)放在 cookie 里可能導(dǎo)致 cookie 超限,那就只好放在 local storage 里。
這樣會(huì)造成安全隱患,因?yàn)?local storage 這類的本地存儲(chǔ)是可以被 JS 直接讀取的。
另外由上文也提到,token 一旦生成無法讓其失效,必須等到其過期才行,這樣的話如果服務(wù)端檢測(cè)到了一個(gè)安全威脅,也無法使相關(guān)的 token 失效。
所以 token 更適合一次性的命令認(rèn)證,設(shè)置一個(gè)比較短的有效期。
誤解:Cookie 相比 token 更不安全,比如 CSRF 攻擊。
首先我們需要解釋下 CSRF 攻擊是怎么回事。
攻擊者通過一些技術(shù)手段欺騙用戶的瀏覽器去訪問一個(gè)自己曾經(jīng)認(rèn)證過的網(wǎng)站并運(yùn)行一些操作(如發(fā)郵件,發(fā)消息,甚至財(cái)產(chǎn)操作如轉(zhuǎn)賬和購(gòu)買商品)。
由于瀏覽器曾經(jīng)認(rèn)證過(cookie 里帶來 sessionId 等身份認(rèn)證的信息),所以被訪問的網(wǎng)站會(huì)認(rèn)為是真正的用戶操作而去運(yùn)行。
比如用戶登錄了某銀行網(wǎng)站(假設(shè)為 http://www.examplebank.com/,并且轉(zhuǎn)賬地址為 http://www.examplebank.com/withdraw?amount=1000&transferTo=PayeeName)。
登錄后 cookie 里會(huì)包含登錄用戶的 sessionid,攻擊者可以在另一個(gè)網(wǎng)站上放置如下代碼:
那么如果正常的用戶誤點(diǎn)了上面這張圖片,由于相同域名的請(qǐng)求會(huì)自動(dòng)帶上 cookie。
而 cookie 里帶有正常登錄用戶的 sessionid,類似上面這樣的轉(zhuǎn)賬操作在 server 就會(huì)成功,會(huì)造成極大的安全風(fēng)險(xiǎn)。
CSRF 攻擊的根本原因在于對(duì)于同樣域名的每個(gè)請(qǐng)求來說,它的 cookie 都會(huì)被自動(dòng)帶上,這個(gè)是瀏覽器的機(jī)制決定的,所以很多人據(jù)此認(rèn)定 cookie 不安全。
使用 token 確實(shí)避免了CSRF 的問題,但正如上文所述,由于 token 保存在 local storage,它會(huì)被 JS 讀取,從存儲(chǔ)角度來看也不安全(實(shí)際上防護(hù) CSRF 攻擊的正確方式是用 CSRF token)。
所以不管是 cookie 還是 token,從存儲(chǔ)角度來看其實(shí)都不安全,都有暴露的風(fēng)險(xiǎn),我們所說的安全更多的是強(qiáng)調(diào)傳輸中的安全,可以用 HTTPS 協(xié)議來傳輸, 這樣的話請(qǐng)求頭都能被加密,也就保證了傳輸中的安全。
其實(shí)我們把 cookie 和 token 比較本身就不合理,一個(gè)是存儲(chǔ)方式,一個(gè)是驗(yàn)證方式,正確的比較應(yīng)該是 session vs token。
總結(jié)
session 和 token 本質(zhì)上是沒有區(qū)別的,都是對(duì)用戶身份的認(rèn)證機(jī)制,只是他們實(shí)現(xiàn)的校驗(yàn)機(jī)制不一樣而已(一個(gè)保存在 server,通過在 redis 等中間件獲取來校驗(yàn),一個(gè)保存在 client,通過簽名校驗(yàn)的方式來校驗(yàn))。
多數(shù)場(chǎng)景上使用 session 會(huì)更合理,但如果在單點(diǎn)登錄,一次性命令認(rèn)證上使用 token 會(huì)更合適,最好在不同的業(yè)務(wù)場(chǎng)景中合理選型,才能達(dá)到事半功倍的效果。
作者:坤哥,前獨(dú)角獸技術(shù)專家,現(xiàn)創(chuàng)業(yè)者,持續(xù)分享個(gè)人的成長(zhǎng)收獲
編輯:陶家龍
出處:轉(zhuǎn)載自公眾號(hào)碼海(ID:seaofcode)
本文題目:Session不香嗎,為什么還要Token?
網(wǎng)頁(yè)URL:http://fisionsoft.com.cn/article/cocpogj.html


咨詢
建站咨詢
