微服務:一步步帶你認知前後端分離利器之JWT

Tomcat Redis Nginx 技術 中央處理器 牛旦教育IT課堂 2019-04-08


微服務:一步步帶你認知前後端分離利器之JWT


一、HTTP的無狀態性

HTTP 是無狀態協議,它不對之前發送過的請求和響應的狀態進行管理。也就是說,無法根據之前的狀態進行本次的請求處理。假設要求登錄認證的 Web 頁面本身無法進行狀態的管理(不記錄已登錄的狀態),那麼每次跳轉新頁面不是要再次登錄,就是要在每次請求報文中附加參數來管理登錄狀態。

不可否認,無狀態協議當然也有它的優點。由於不必保存狀態,自然可減少服務器的 CPU 及內存資源的消耗。從另一側面來說,也正是因為 HTTP 協議本身是非常簡單的,所以才會被應用在各種場景裡。

二、Cookie 技術的引入

如果讓服務器管理全部客戶端狀態則會成為負擔,保留無狀態協議這個特徵的同時又要解決類似的矛盾問題,於是引入了 Cookie 技術。Cookie 技術通過在請求和響應報文中寫入Cookie信息來控制客戶端的狀態。

Cookie會根據從服務器端發送的響應報文內的一個叫做Set-Cookie 的首部字段信息,通知客戶端保存 Cookie。當下次客戶端再往該服務器發送請求時,客戶端會自動在請求報文中加入Cookie 值後發送出去。

1、沒有 Cookie 信息狀態下的請求(圖片來源《圖解HTTP》)

微服務:一步步帶你認知前後端分離利器之JWT


2、第 2 次以後(存有 Cookie 信息狀態) 的請求(圖片來源《圖解HTTP》)

微服務:一步步帶你認知前後端分離利器之JWT


3、詳細介紹Cookie 傳輸過程

微服務:一步步帶你認知前後端分離利器之JWT


服務器端發現客戶端發送過來的 Cookie 後, 會去檢查究竟是從哪一個客戶端發來的連接請求, 然後對比服務器上的記錄, 最後得到之前的狀態信息。

三、基於表單的認證

目前用戶的認證多半是基於表單的認證,基於表單的認證一般會使用 Cookie 來管理Session(Session會話,Session代表著服務器和客戶端一次會話的過程,直到Session失效(服務端關閉)或者客戶端關閉時結束)。基於表單認證本身是通過服務器端的 Web應用,將客戶端發送過來的用戶ID和密碼與之前登錄過的信息做匹配來進行認證的。

但鑑於 HTTP 是無狀態協議, 之前已認證成功的用戶狀態無法通過協議層面保存下來。 即無法實現狀態管理, 因此即使當該用戶下一次繼續訪問,也無法區分他與其他的用戶。於是我們會使用Cookie 來管理 Session,以彌補 HTTP 協議中不存在的狀態管理功能。

微服務:一步步帶你認知前後端分離利器之JWT


簡單的來說就是,用戶在登錄的時候,會在Web服務器中開闢一段內存空間Session用於保存用戶的認證信息和其他信息,用戶登錄成功之後會通過Set-Cookie的首部字段信息,通知客戶端保存Cookie,而這Cookie保存的就是服務器端Session的ID,下次請求的時候客戶端會帶上該Cookie向服務器端發送請求,服務器端進行校驗,如果Session中保存的有該ID的Session就表示用戶認證通過,否則失敗!

微服務:一步步帶你認知前後端分離利器之JWT


四、Session存儲位置以及集群情況下的問題

Session 是存儲在Web服務器(例如:Tomcat)中的,並針對每個客戶端(客戶),通過SessionID來區別不同用戶的。Session是以Cookie技術或URL重寫實現,默認以Cookie技術實現,服務端會給這次會話創造一個JSESSIONID的Cookie值。

但是一個顯著的問題就是,在集群模式下如果通過Nginx負載均衡的時候,如果有一個用戶登錄的時候請求被分配到服務器A上,登錄成功後設置的Session就會存放在服務器A上了,但是在服務器B上卻沒有該用戶的Session數據,當用戶再次發起一個請求的時候,此時請求如果被分配到服務器B上,則就不會查詢到該用戶的登錄狀態,就會出現登錄失敗的情況!

一種可以想到的方式就是將多個Web服務器上存儲的Session統一存儲到某一存儲介質中,保證進集群中的每一臺機器都可以看到所有相同Session數據,這裡的同步體現在所有的Session存儲在同一的存儲介質裡邊。

幸運的是我們常用的Tomcat容器已經為我們提供了一個接口,可以讓我們實現將Session存儲到除當前服務器之外的其他存儲介質上,例如Redis等。


微服務:一步步帶你認知前後端分離利器之JWT


瞭解Spring Session的小夥伴可能都會知道Spring Session的本質就是通過實現Tomcat提供的該接口將Session存儲到Redis中,以此來實現Session的統一存儲管理,對Spring Session有興趣的小夥伴可以參考往期的文章:

1、使用Redis存儲Nginx+Tomcat負載均衡集群的Session

2、使用Spring Session和Redis解決分佈式Session跨域共享問題

3、Spring Session解決分佈式Session問題的實現原理

五、小結與需求痛點

Session和Cookie的目的相同,都是為了克服HTTP協議無狀態的缺陷,但完成的方法不同。Session通過Cookie,在客戶端保存SessionID,而將用戶的其他會話消息保存在服務端的Session對象中,與此相對的,Cookie需要將所有信息都保存在客戶端。因此Cookie存在著一定的安全隱患,例如本地Cookie中保存的用戶名密碼被破譯,或Cookie被其他網站收集,例如:

  1. appA主動設置域B cookie,讓域B cookie獲取;
  2. XSS,在appA上通過JavaScript獲取document.cookie,並傳遞給自己的appB。

上述過程我們簡單的描述了Session的演進過程還有使用同步的方式解決Session在集群的時候出現的問題,但是我們意識到了使用Spring Session的方式來實現Session的同步是一件相對比較麻煩的事情,我們雖然使用Redis來進行同步,但是Redis並不是100%可靠的,我們需要對Redis搭建集群、進行主從同步複製、進行持久化等,顯然這是一件很複雜的事情,因此有沒有一種小而輕便的方式來實現我們的這種認證需求!那就是JWT了!

除了上述我們遇到的問題之外,在目前前後端分離的大環境下經常會遇到需要根據用戶來分配權限和顯示相對應信息的問題,雖然傳統的Cookie和Session機制可以解決這個問題,但就通用性而言,JWT(JSON Web Token)相對來說更好。

看到這裡很多小夥伴都已經按捺不住了!那JWT到底是什麼呢?

六、JWT是什麼

Json web token (JWT),是為了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準((RFC 7519)。該標準被設計為緊湊且安全的,一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也可以增加一些額外的其它業務邏輯所必須的聲明信息。當然該標準也可直接被用於認證,也可被加密。

JWT的幾個特點:

1、由於它們的尺寸較小,JWT可以通過URL,POST參數或HTTP頭部發送。 另外,尺寸越小意味著傳輸速度越快。

2、有效載荷包含有關用戶的所有必需信息,避免了多次查詢數據庫的需要。

JWT的使用場景:

1、驗證

這是使用JWT最常見的情況。 一旦用戶登錄,每個後續請求將包括JWT。它將允許用戶訪問該令牌允許的路由,服務和資源。 單點登錄是當今廣泛使用JWT的一項功能,因為它的開銷很小,而且能夠輕鬆地跨不同域使用。

2、信息交換

JWT是在各方之間安全傳輸信息的好方法, 因為JWT可以被簽名(例如使用公鑰/私鑰對進行簽名)。所以你可以確定發件人是他們說的那個人。 此外,由於使用頭部(header)和有效載荷(payload)計算簽名,因此您還可以驗證內容是否未被篡改。

七、JWT的結構說明

JWT包含三個由點(.)分隔的部分,它們是:

  1. 頭部(header)
  2. 有效負載(payload)
  3. 簽名(signature)

因此,JWT通常看起來如下所示:

xxxxx.yyyyy.zzzzz

1、頭部(header)

頭部(header)通常由兩部分組成:令牌的類型(即JWT)和正在使用的散列算法(如HMAC SHA256或RSA)。如下所示:

微服務:一步步帶你認知前後端分離利器之JWT


然後,將這個JSON用Base64編碼,形成JWT的第一部分。

2、有效負載(payload)

令牌的第二部分是包含聲明的有效載荷。 聲明是關於實體(通常是用戶)和附加元數據的聲明。 有三種類型的聲明:

  1. 標準中註冊的聲明;
  2. 公開聲明;
  3. 私人聲明;

(1)標準中註冊的聲明:這是一組預先定義的聲明,這些聲明不是強制性的,但建議提供一套有用的,可互操作的聲明。 如下:

iss: jwt簽發者sub: jwt所面向的用戶aud: 接收jwt的一方exp: jwt的過期時間,這個過期時間必須要大於簽發時間nbf: 定義在什麼時間之前,該jwt都是不可用的.iat: jwt的簽發時間jti: jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊。

注意:聲明名稱只有三個字符長,因為JWT是緊湊的。

(2)公開聲明:這些可以由使用JWT的人員隨意定義。 但為避免衝突,應在IANA JSON Web令牌註冊表中定義它們,或者將其定義為包含防衝突命名空間的URI。

(3)私人聲明:這是為了共享使用它們的當事方之間共享信息而創建的聲明,既不是登記聲明,也不是公開聲明。

示例如下:

微服務:一步步帶你認知前後端分離利器之JWT


然後將有效載荷進行Base64編碼,以形成JSON Web令牌的第二部分。

3、簽名(signature)

要創建簽名部分,您必須採用頭部(header),有效載荷(payload),密鑰(secret),以及頭部中指定的算法。例如,如果你想使用HMAC SHA256算法,簽名將按以下方式創建:

微服務:一步步帶你認知前後端分離利器之JWT


簽名通常用於驗證JWT的發件人是誰,並JWT在傳送的過程中不被篡改。

注意:上圖紅框中的secret是保存在服務器端的,JWT的簽發生成也是在服務器端的,secret就是用來進行JWT的簽發和jwt的驗證,所以,它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret,那就意味著客戶端是可以自我簽發jwt了。

4、案例演示

下面顯示了一個登錄請求成功之後服務端返回的Token,它由編碼頭部(header)、編碼有效載荷(payload)和簽名(signature)通過(.)拼接而成:

微服務:一步步帶你認知前後端分離利器之JWT


微服務:一步步帶你認知前後端分離利器之JWT


如果需要,你可以使用jwt.io的Debugger工具,來編碼、驗證和生成JWT。操作界面如下:

微服務:一步步帶你認知前後端分離利器之JWT


八、JWT的工作原理

在身份驗證中,當用戶使用他們的憑證(如用戶名、密碼)成功登錄時,後臺服務器將返回一個token,前端接收到這個token將其保存在本地(通常在本地存儲中,也可以使用Cookie,但不是傳統方法中創建會話,服務器並返回一個cookie)。下次用戶想要訪問受保護的路由或資源時,就將本地保存的token放在頭部Header中發送到後臺服務器。服務器接收到請求,檢查頭部中token的存在,如果存在就允許訪問受保護的路由或資源,否則就不允許。如下所示:

微服務:一步步帶你認知前後端分離利器之JWT


一般默認的Value是以“Bearer ”開始,注意這裡的Bearer之後有一個空格,以便後端進行分割。

這是一種無狀態身份驗證機制,因為用戶狀態永遠不會保存在服務器內存中。 由於JWT是獨立的,所有必要的信息都在那裡,所以減少了多次查詢數據庫的需求。

微服務:一步步帶你認知前後端分離利器之JWT


九、總結

1、優點

(1)因為Json的通用性,所以JWT是可以進行跨語言支持的,像Java、JavaScript、NodeJS、PHP等很多語言都可以使用。

(2)因為有了payload部分,所以JWT可以在自身存儲一些其他業務邏輯所必要的非敏感信息。

(3)便於傳輸,JWT的構成非常簡單,字節佔用很小,所以它是非常便於傳輸的。

(4)它不需要在服務端保存會話信息, 所以它易於應用的擴展

2、安全相關

(1)不應該在JWT的payload部分存放敏感信息,因為該部分是客戶端可解密的部分。

(2)保護好secret私鑰,該私鑰非常重要。

(3)如果可以,請使用HTTPS協議,不!是務必使用HTTPS!



後續合適時機會有兩至三篇文章介紹JWT的使用和JWT的優缺點以及如何保證token的安全性等,敬請期待!

參考文章:1、https://www.bysocket.com/?p=362 2、https://www.bysocket.com/?p=384 3、服務器前後端分離之JWT用戶認證 4、部分截圖和內容參考《圖解HTTP》原文地址:https://mp.weixin.qq.com/s/R9UMjxJs8zZVnPO1DaUpuA所有權歸原著者。

相關推薦

推薦中...