Session/Cookie/Token 還傻傻分不清?

2022-01-01 CodeSheep

相信項目中用JWT Token的應該不在少數,但是發現網上很多文章對 token 的介紹有誤,所以對 cookie,session, token 作了一下對比(文中token指jwt token)相信大家看完肯定有收穫!Cookie

1991 年 HTTP 0.9 誕生了,當時只是為了滿足大家瀏覽 web 文檔的要求 ,所以只有 GET 請求,瀏覽完了就走了,兩個連接之間是沒有任何聯繫的,這也是 HTTP 為無狀態的原因,因為它誕生之初就沒有這個需求。

但隨著交互式 Web 的興起(所謂交互式就是你不光可以瀏覽,還可以登錄,發評論,購物等用戶操作的行為),單純地瀏覽 web 已經無法滿足人們的要求,比如隨著網上購物的興起,需要記錄用戶的購物車記錄,就需要有一個機制記錄每個連接的關係,這樣我們就知道加入購物車的商品到底屬於誰了,於是 Cookie 就誕生了。

Cookie,有時也用其複數形式 Cookies。類型為「小型文本文件」,是某些網站為了辨別用戶身份,進行 Session 跟蹤而儲存在用戶本地終端上的數據(通常經過加密),由用戶客戶端計算機暫時或永久保存的信息 。

工作機制如下

img

以加入購物車為例,每次瀏覽器請求後 server 都會將本次商品 id 存儲在 Cookie 中返回給客戶端,客戶端會將 Cookie 保存在本地,下一次再將上次保存在本地的 Cookie 傳給 server 就行了,這樣每個 Cookie 都保存著用戶的商品 id,購買記錄也就不會丟失了

img

仔細觀察上圖相信你不難發現隨著購物車內的商品越來越多,每次請求的 cookie 也越來越大,這對每個請求來說是一個很大的負擔,我只是想將一個商品加入購買車,為何要將歷史的商品記錄也一起返回給 server ?購物車信息其實已經記錄在 server 了,瀏覽器這樣的操作豈不是多此一舉?怎麼改進呢

Session

仔細考慮下,由於用戶的購物車信息都會保存在 Server 中,所以在 Cookie 裡只要保存能識別用戶身份的信息,知道是誰發起了加入購物車操作即可,這樣每次請求後只要在 Cookie 裡帶上用戶的身份信息,請求體裡也只要帶上本次加入購物車的商品 id,大大減少了 cookie 的體積大小,我們把這種能識別哪個請求由哪個用戶發起的機制稱為 Session(會話機制),生成的能識別用戶身份信息的字符串稱為 sessionId,它的工作機制如下

img首先用戶登錄,server 會為用戶生成一個 session,為其分配唯一的 sessionId,這個 sessionId 是與某個用戶綁定的,也就是說根據此 sessionid(假設為 abc) 可以查詢到它到底是哪個用戶,然後將此 sessionid 通過 cookie 傳給瀏覽器之後瀏覽器的每次添加購物車請求中只要在 cookie 裡帶上 sessionId=abc 這一個鍵值對即可,server 根據 sessionId 找到它對應的用戶後,把傳過來的商品 id 保存到 server 中對應用戶的購物車即可

可以看到通過這種方式再也不需要在 cookie 裡傳所有的購物車的商品 id 了,大大減輕了請求的負擔!

另外通過上文不難觀察出 cookie 是存儲在 client 的,而 session 保存在 server,sessionId 需要藉助 cookie 的傳遞才有意義。

session 的痛點

看起來通過  cookie + session 的方式是解決了問題, 但是我們忽略了一個問題,上述情況能正常工作是因為我們假設 server 是單機工作的,但實際在生產上,為了保障高可用,一般伺服器至少需要兩臺機器,通過負載均衡的方式來決定到底請求該打到哪臺機器上。

balance

如圖示:客戶端請求後,由負載均衡器(如 Nginx)來決定到底打到哪臺機器

假設登錄請求打到了 A 機器,A 機器生成了 session 並在 cookie 裡添加 sessionId 返回給了瀏覽器,那麼問題來了:下次添加購物車時如果請求打到了 B 或者 C,由於 session 是在 A 機器生成的,此時的 B,C 是找不到 session 的,那麼就會發生無法添加購物車的錯誤,就得重新登錄了,此時請問該怎麼辦。主要有以下三種方式

1、session 複製

A 生成 session 後複製到 B, C,這樣每臺機器都有一份 session,無論添加購物車的請求打到哪臺機器,由於 session 都能找到,故不會有問題

balance (1)

這種方式雖然可行,但缺點也很明顯:

同一樣的一份 session 保存了多份,數據冗餘如果節點少還好,但如果節點多的話,特別是像阿里,微信這種由於 DAU 上億,可能需要部署成千上萬臺機器,這樣節點增多複製造成的性能消耗也會很大。

2、session 粘連

這種方式是讓每個客戶端請求只打到固定的一臺機器上,比如瀏覽器登錄請求打到 A 機器後,後續所有的添加購物車請求也都打到 A 機器上,Nginx 的 sticky 模塊可以支持這種方式,支持按 ip 或 cookie 粘連等等,如按 ip 粘連方式如下

upstream tomcats {
  ip_hash;
  server 10.1.1.107:88;
  server 10.1.1.132:80;
}

img

這樣的話每個 client 請求到達 Nginx 後,只要它的 ip 不變,根據 ip hash 算出來的值會打到固定的機器上,也就不存在 session 找不到的問題了,當然不難看出這種方式缺點也是很明顯,對應的機器掛了怎麼辦?


3、session 共享

這種方式也是目前各大公司普遍採用的方案,將 session 保存在 redis,memcached 等中間件中,請求到來時,各個機器去這些中間件取一下 session 即可。

img

缺點其實也不難發現,就是每個請求都要去 redis 取一下 session,多了一次內部連接,消耗了一點性能,另外為了保證 redis 的高可用,必須做集群,當然了對於大公司來說, redis 集群基本都會部署,所以這方案可以說是大公司的首選了。

Token:no session!

通過上文分析我們知道通過在服務端共享 session 的方式可以完成用戶的身份定位,但是不難發現也有一個小小的瑕疵:搞個校驗機制我還得搭個 redis 集群?大廠確實 redis 用得比較普遍,但對於小廠來說可能它的業務量還未達到用 redis 的程度,所以有沒有其他不用 server 存儲 session 的用戶身份校驗機制呢,這就是我們今天要介紹的主角:token。

首先請求方輸入自己的用戶名,密碼,然後 server 據此生成 token,客戶端拿到 token 後會保存到本地,之後向 server 請求時在請求頭帶上此 token 即可。

img

相信大家看了上圖會發現存在兩個問題

1、 token 只存儲在瀏覽器中,服務端卻沒有存儲,這樣的話我隨便搞個 token 傳給 server 也行?

答:server 會有一套校驗機制,校驗這個 token 是否合法。

2、怎麼不像 session 那樣根據 sessionId 找到 userid 呢,這樣的話怎麼知道是哪個用戶?

答:token 本身攜帶 uid 信息

第一個問題,如何校驗 token 呢?我們可以借鑑 HTTPS 的籤名機制來校驗。先來看 jwt token 的組成部分

img

可以看到 token 主要由三部分組成

payload:可以指定用戶 id,過期時間等非敏感數據Signature: 籤名,server 根據 header 知道它該用哪種籤名算法,再用密鑰根據此籤名算法對 head + payload 生成籤名,這樣一個 token 就生成了。

當 server 收到瀏覽器傳過來的 token 時,它會首先取出 token 中的 header + payload,根據密鑰生成籤名,然後再與 token 中的籤名比對,如果成功則說明籤名是合法的,即 token 是合法的。而且你會發現 payload 中存有我們的 userId,所以拿到 token 後直接在 payload 中就可獲取 userid,避免了像 session 那樣要從 redis 去取的開銷

畫外音:header, payload 實際上是以 base64 的形式存在的,文中為了描述方便,省去了這一步。

你會發現這種方式確實很妙,只要 server 保證密鑰不洩露,那麼生成的 token 就是安全的,因為如果偽造 token 的話在籤名驗證環節是無法通過的,就此即可判定 token 非法。

可以看到通過這種方式有效地避免了 token 必須保存在 server 的弊端,實現了分布式存儲,不過需要注意的是,token 一旦由 server 生成,它就是有效的,直到過期,無法讓 token 失效,除非在 server 為 token 設立一個黑名單,在校驗 token 前先過一遍此黑名單,如果在黑名單裡則此  token 失效,但一旦這樣做的話,那就意味著黑名單就必須保存在 server,這又回到了 session 的模式,那直接用 session 不香嗎。所以一般的做法是當客戶端登出要讓 token 失效時,直接在本地移除 token 即可,下次登錄重新生成 token 就好。

另外需要注意的是 token 一般是放在 header 的 Authorization 自定義頭裡,不是放在 Cookie 裡的,這主要是為了解決跨域不能共享 Cookie 的問題 (下文詳述)

Cookie 與 Token 的簡單總結

Cookie 有哪些局限性?

1、 Cookie 跨站是不能共享的,這樣的話如果你要實現多應用(多系統)的單點登錄(SSO),使用 Cookie 來做需要的話就很困難了(要用比較複雜的 trick 來實現,有興趣的話可以看文末參考連結)

畫外音: 所謂單點登錄,是指在多個應用系統中,用戶只需要登錄一次就可以訪問所有相互信任的應用系統。

但如果用 token 來實現 SSO 會非常簡單,如下

img

只要在 header 中的 authorize 欄位(或其他自定義)加上 token 即可完成所有跨域站點的認證。

2、 在移動端原生請求是沒有 cookie 之說的,而 sessionid 依賴於 cookie,sessionid 就不能用 cookie 來傳了,如果用 token 的話,由於它是隨著 header 的 authoriize 傳過來的,也就不存在此問題,換句話說token 天生支持移動平臺,可擴展性好

綜上所述,token 具有存儲實現簡單,擴展性好這些特點。

token 有哪些缺點

那有人就問了,既然 token 這麼好,那為什麼各個大公司幾乎都採用共享 session 的方式呢,可能很多人是第一次聽到 token,token 不香嗎? token 有以下兩點劣勢:

1、 token 太長了

token 是 header, payload 編碼後的樣式,所以一般要比 sessionId 長很多,很有可能超出 cookie 的大小限制(cookie 一般有大小限制的,如 4kb),如果你在 token 中存儲的信息越長,那麼 token 本身也會越長,這樣的話由於你每次請求都會帶上 token,對請求來是個不小的負擔

2、 不太安全

網上很多文章說 token 更安全,其實不然,細心的你可能發現了,我們說 token 是存在瀏覽器的,再細問,存在瀏覽器的哪裡?既然它太長放在 cookie 裡可能導致 cookie 超限,那就只好放在 local storage 裡,這樣會造成安全隱患,因為 local storage 這類的本地存儲是可以被 JS 直接讀取的,另外由上文也提到,token 一旦生成無法讓其失效,必須等到其過期才行,這樣的話如果服務端檢測到了一個安全威脅,也無法使相關的 token 失效。

所以 token 更適合一次性的命令認證,設置一個比較短的有效期

誤解: Cookie 相比 token 更不安全,比如 CSRF 攻擊

首先我們需要解釋下 CSRF 攻擊是怎麼回事

攻擊者通過一些技術手段欺騙用戶的瀏覽器去訪問一個自己曾經認證過的網站並運行一些操作(如發郵件,發消息,甚至財產操作如轉帳和購買商品)。由於瀏覽器曾經認證過(cookie 裡帶來 sessionId 等身份認證的信息),所以被訪問的網站會認為是真正的用戶操作而去運行。

比如用戶登錄了某銀行網站(假設為 http://www.examplebank.com/,並且轉帳地址為 http://www.examplebank.com/withdraw?amount=1000&transferTo=PayeeName),登錄後 cookie 裡會包含登錄用戶的 sessionid,攻擊者可以在另一個網站上放置如下代碼

<img src="http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman">

那麼如果正常的用戶誤點了上面這張圖片,由於相同域名的請求會自動帶上 cookie,而 cookie 裡帶有正常登錄用戶的 sessionid,類似上面這樣的轉帳操作在 server 就會成功,會造成極大的安全風險

csrf 示意圖

CSRF 攻擊的根本原因在於對於同樣域名的每個請求來說,它的 cookie 都會被自動帶上,這個是瀏覽器的機制決定的,所以很多人據此認定 cookie 不安全。

使用 token 確實避免了CSRF 的問題,但正如上文所述,由於 token 保存在 local storage,它會被 JS 讀取,從存儲角度來看也不安全(實際上防護 CSRF 攻擊的正確方式是用 CSRF token)

所以不管是 cookie 還是 token,從存儲角度來看其實都不安全,都有暴露的風險,我們所說的安全更多的是強調傳輸中的安全,可以用 HTTPS 協議來傳輸, 這樣的話請求頭都能被加密,也就保證了傳輸中的安全。

其實我們把 cookie 和 token 比較本身就不合理,一個是存儲方式,一個是驗證方式,正確的比較應該是 session vs token。

總結

session 和 token 本質上是沒有區別的,都是對用戶身份的認證機制,只是他們實現的校驗機制不一樣而已(一個保存在 server,通過在 redis 等中間件獲取來校驗,一個保存在 client,通過籤名校驗的方式來校驗),多數場景上使用 session 會更合理,但如果在單點登錄,一次性命令認證上使用 token 會更合適,最好在不同的業務場景中合理選型,才能達到事半功倍的效果。

巨人的肩膀

Cookie Session跨站無法共享問題(單點登錄解決方案):https://blog.csdn.net/wtopps/article/details/75040224http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/

相關焦點

  • 還傻傻分不清 Cookie、Session、Token、JWT?
    因為 session 是由單個伺服器創建的,但是處理用戶請求的伺服器不一定是那個創建 session 的伺服器,那麼該伺服器就無法拿到之前已經放入到 session 中的登錄憑證之類的信息了。當多個應用要共享 session 時,除了以上問題,還會遇到跨域問題,因為不同的應用可能部署的主機不一樣,需要在各個應用做好 cookie 跨域的處理。
  • 你還傻傻分不清Cookie、Session、Token、JWT嗎?
    因為 session 是由單個伺服器創建的,但是處理用戶請求的伺服器不一定是那個創建 session 的伺服器,那麼該伺服器就無法拿到之前已經放入到 session 中的登錄憑證之類的信息了。當多個應用要共享 session 時,除了以上問題,還會遇到跨域問題,因為不同的應用可能部署的主機不一樣,需要在各個應用做好 cookie 跨域的處理。
  • 還分不清 Cookie、Session、Token、JWT?一篇文章講清楚
    因為 session 是由單個伺服器創建的,但是處理用戶請求的伺服器不一定是那個創建 session 的伺服器,那麼該伺服器就無法拿到之前已經放入到 session 中的登錄憑證之類的信息了。當多個應用要共享 session 時,除了以上問題,還會遇到跨域問題,因為不同的應用可能部署的主機不一樣,需要在各個應用做好 cookie 跨域的處理。
  • cookie、session、token區別
    token,session,cookie的概念和區別1.token是 服務經過計算發給客戶端的,服務不保存,每次客戶端來請求,經過解密等計算來驗證是否是自己下發的2.session是服務本地保存,發給客戶端,客戶端每次訪問都帶著,直接和服務的session比對3.cookie是保存在客戶端上的一些基本信息,服務不保存,每次請求時客戶端帶上cookie,裡面有一些帳戶密碼
  • 詳解 Cookie,Session,Token
    本文主要講解cookie,session, token 這三種是如何管理會話的;cookiecookie 是一個非常具體的東西,指的就是瀏覽器裡面能永久存儲的一種數據。跟伺服器沒啥關係,僅僅是瀏覽器實現的一種數據存儲功能。
  • Cookie、Session、Token與JWT解析
    如果要進行類似論壇登陸相關的操作,就實現不了了。session 是基於 cookie 實現的,session 存儲在伺服器端,sessionId 會被存儲到客戶端的cookie 中。ps:還有一種是瀏覽器禁用了cookie或不支持cookie,這種可以通過URL重寫的方式發到伺服器;
  • 面試必問:session,cookie和token的區別
    session,token作為面試必問題,很多同學能答個大概,但是又迷糊不清,希望本篇文章對大家有所幫助http是一個無狀態協議什麼是無狀態呢?禁用cookie後還有其他方法存儲,比如放在url中現在大多都是Session + Cookie,但是只用session不用cookie,或是只用cookie,不用session在理論上都可以保持會話狀態。可是實際中因為多種原因,一般不會單獨使用用session只需要在客戶端保存一個id,實際上大量數據都是保存在服務端。
  • 前後端接口鑑權全解 Cookie/Session/Token 的區別
    例如登錄之後,伺服器給你一個標誌,就存在 cookie 裡,之後再連接時,都會自動帶上 cookie,伺服器便分清誰是誰。另外,cookie 還可以用於跟蹤一個用戶,這就產生了隱私問題,於是也就有了「禁用 cookie」這個選項(然而現在這個時代禁用 cookie 是挺麻煩的事情)。
  • 徹底理解Cookie,Session,Token
    有時候會採用一點小伎倆:session sticky , 就是讓小F的請求一直粘連在機器A上, 但是這也不管用, 要是機器A掛掉了, 還得轉到機器B去。那隻好做session 的複製了, 把session id 在兩個機器之間搬來搬去, 快累死了。
  • Cookie、Session、Token、JWT終於講清楚了
    因為 session 是由單個伺服器創建的,但是處理用戶請求的伺服器不一定是那個創建 session 的伺服器,那麼該伺服器就無法拿到之前已經放入到 session 中的登錄憑證之類的信息了。當多個應用要共享 session 時,除了以上問題,還會遇到跨域問題,因為不同的應用可能部署的主機不一樣,需要在各個應用做好 cookie 跨域的處理。
  • 黑客都知道的Cookie、Session、Token、JWT
    因為 session 是由單個伺服器創建的,但是處理用戶請求的伺服器不一定是那個創建 session 的伺服器,那麼該伺服器就無法拿到之前已經放入到 session 中的登錄憑證之類的信息了。當多個應用要共享 session 時,除了以上問題,還會遇到跨域問題,因為不同的應用可能部署的主機不一樣,需要在各個應用做好 cookie 跨域的處理。
  • 熬夜徹底搞懂Cookie Session Token JWT
    說得通俗一點:HTTP 的記憶力不太好,需要藉助『助聽器』。記憶力不好的優點就是一個字「快」;缺點也很明顯,需要借靠 cookie、session 、token等機制將客戶端多次請求關聯起來。想像一下如果沒有 cookie、session、token 這樣的機制,我們在網站上每次點擊都需要重新輸入密碼認證,這樣槽糕的體驗你還願意繼續用嗎?在講解cookie、session 、token前我們先簡單講解兩個概念:認證、授權。什麼是認證?
  • Cookie, Session, Token,WebStorage你懂多少?
    很多同學都不知道,或者簡單背一個答案,曰無狀態.再問,那麼HTTP協議如何保持狀態?只有一小部分同學能答出來要用cookie和session.再問,有了cookie為什麼還要session?更少一部分人能答出來安全。再問,不要cookie行不行?能答出URL重寫的已經不錯了。對於高級的測試,再繼續深入,session保存在伺服器內存中,隨著用戶的增多,伺服器撐不住了怎麼辦?
  • 測開之Cookie、Session、Token、JWT的區別
    >cookie 無法跨域一個瀏覽器針對一個網站最多存 20 個Cookie,瀏覽器一般只允許存放 300 個Cookie移動端對 cookie 的支持不是很好,而 session 需要基於 cookie 實現,所以移動端常用的是 token使用 session 時需要考慮的問題
  • 一文帶你徹底搞懂Cookie、Session和Token
    HTTP無狀態訪問在90年代,瀏覽器剛出現的時候,它的作用也僅僅是瀏覽一些文本而已,彼此之間不存在依賴關係(在後面的學習中,我們把這種依賴關係理解為會話)。因此,伺服器不需要做任何的記錄,瀏覽器請求什麼,伺服器就返回什麼,彼此之間清清楚楚,不存在任何的愛恨情仇,雙方的關係非常的融洽。
  • 面試官:要不講講 Cookie、Session、Token、JWT之間的區別?
    因為 session 是由單個伺服器創建的,但是處理用戶請求的伺服器不一定是那個創建 session 的伺服器,那麼該伺服器就無法拿到之前已經放入到 session 中的登錄憑證之類的信息了。當多個應用要共享 session 時,除了以上問題,還會遇到跨域問題,因為不同的應用可能部署的主機不一樣,需要在各個應用做好 cookie 跨域的處理。
  • 一文讀懂Cookie、Session、Token和JWT(建議收藏)
    因為 session 是由單個伺服器創建的,但是處理用戶請求的伺服器不一定是那個創建 session 的伺服器,那麼該伺服器就無法拿到之前已經放入到 session 中的登錄憑證之類的信息了。當多個應用要共享 session 時,除了以上問題,還會遇到跨域問題,因為不同的應用可能部署的主機不一樣,需要在各個應用做好 cookie 跨域的處理。
  • 前端鑑權 5 兄弟:cookie、session、token、jwt、單點登錄
    配置:Expires / Max-Age你畢業了卡就不好使了。cookie 還可以限制::「時間範圍」::,通過 Expires、Max-Age 中的一種。Expires屬性指定一個具體的到期時間,到了指定時間以後,瀏覽器就不再保留這個 Cookie。它的值是 UTC 格式。
  • 前端鑑權:cookie、session、token、jwt、單點登錄全貢獻出來了!
    cookie 還可以限制「時間範圍」,通過 Expires、Max-Age 中的一種。Expires 屬性指定一個具體的到期時間,到了指定時間以後,瀏覽器就不再保留這個 Cookie。它的值是 UTC 格式。如果不設置該屬性,或者設為 null,Cookie 只在當前會話(session)有效,瀏覽器窗口一旦關閉,當前 Session 結束,該 Cookie 就會被刪除。
  • Web基礎技術 | Cookie、Session和Token認證
    Cookie:cookie是伺服器發送給客戶端的用於驗證某一會話信息的數據,cookie中有很多欄位。不同網站Cookie中欄位是不一樣的,是由伺服器端設置的。Cookie中常放入session_id 或者 token 用來驗證會話的登錄狀態。Cookie為什麼能驗證登錄狀態?