我的師父把 「JWT 令牌」玩到了極致
?你好,我是悟空。
我的師父是唐玄奘~
西游記的故事想必大家在暑假看過很多遍了,為了取得真經,唐玄奘歷經苦難,終于達成。
在途經各國的時候,唐玄奘都會拿出一個通關文牒交給當地的國王進行蓋章,方能通過。
本篇目錄如下:
通關文牒
通關文牒就是唐朝官方發的一個憑證,證明持有人來自東土大唐,一般是使臣持有。
有了這個憑證后,到其他國家,比如女兒國國王看到這個憑證后,就會放行。
下面來一張西游記中通關文牒的生命周期圖。
長安是一個頒發憑證(通關文牒)的微服務節點,烏雞國、女兒國和大雷音寺等都是集群中的一個微服務節點,唐玄奘拿著憑證訪問各國。
那為什么別的國家認可這個憑證呢?
那是因為當時的唐朝非常強大,有很多國家都要向唐朝朝貢,與唐朝交好有很多好處的~
朝貢也有篇故事哦~唐太宗把微服務的“心跳機制”玩到了極致!
唐太宗在通關文牒上寫道:“倘到西邦諸國,不滅善緣,照牒放行,須至牒者?!?/p>
意思就是說唐玄奘法師是我們唐朝的使臣,如果途經諸侯國,希望大家放行。
貞觀之治時期的唐朝是在經濟文化上都無比繁盛,國力強盛,周邊國家都希望和唐朝建立友好關系,看到是唐朝使臣來了,好生招待下,然后蓋章放行,給唐朝留個好印象。
在安全架構中,憑證 出現得太頻繁了,比如我們在網關這一層加的校驗令牌,其實就是校驗憑證。
憑證是什么
憑證(Credentials)的出現就是系統保證它與用戶之間的承諾是雙方當時真實意圖的體現,是準確、完整且不可抵賴的。
那唐太宗給唐玄奘的通關文牒就是一個憑證,上面蓋著唐朝的官印、唐太宗的親筆,這充分體現了持有者是擁有一個可信的令牌的,而且這個通關文牒上的官印是不可篡改的,如果改了,其他國家就不認了。
上面這種模式其實對應的是一種普通的認證授權模式,而大名鼎鼎的 OAuth 2.0 認證授權模式雖然有五種模式,但他們殊途同歸,最后的目的都是生成一個憑證給到客戶端,讓客戶端持有這個憑證來訪問資源。關于 OAuth2.0 本篇不做展開。
關于憑證的存儲方案,業界的安全架構中有兩種方案:
- Cookie-Session 模式
- JWT 方案
Cookie-Session 模式
流程圖如下:
用戶登錄認證通過后,后端會存放該客戶端的身份信息,也就是存放到 session 中,session 可以用來區分不同,然后返回一個 sessionId 給到客戶端。
客戶端將 sessionId 緩存在客戶端。當客戶端下次發送 HTTP 請求時,在 header 的 cookie 字段附帶著 sessionId 發送給后端服務器。
后端服務器拿到 header 中的 sessionId,然后根據 sessionId 找到 session,如果 session 存在,則從 session 中解析出用戶的身份信息,然后執行業務邏輯。
我們都知道 HTTP 協議是一種無狀態的傳輸協議,無狀態表示對一個事務的處理沒有上下文的記憶能力,每一個 HTTP 請求都是完全獨立的。但是 Cookie-Seesion 模式卻和 HTTP 無狀態特性相悖,因為客戶端訪問資源時,是攜帶第一次拿到的 sessionId 的,讓服務端能夠順利區分出發送請求的用戶是誰。
服務端對 session 的管理,就是一種狀態管理機制,該機制存儲了每個在線用戶的上下文狀態,再加上一些超時自動清理的管理措施。Cookie-Session 也是最傳統但今天依舊應用到大量系統中,由服務端與客戶端聯動來完成的狀態管理機制。
放到西游記中,如果用這種 Cookie-Session 模式是怎么樣的呢?
我們把唐朝和周邊國家想想成一個分布式集群?,所有國家都需要將唐玄奘這個使者信息都保存一份(分布式存儲),當唐玄奘路過某個國家時,需要查詢本地存儲中是否有唐玄奘,如果有,則認為唐玄奘是合法的使者,可以放行。
但是這種方式就會需要每個國家都同步保存,同步的成本是非常高昂的,而且會有同步延遲的存在。
Cookie-Session 模式的優勢
狀態信息都存儲于服務器,只要依靠客戶端的同源策略和 HTTPS 的傳輸層安全,保證 Cookie 中的鍵值不被竊取而出現被冒認身份的情況,就能完全規避掉上下文信息在傳輸過程中被泄漏和篡改的風險。Cookie-Session 方案的另一大優點是服務端有主動的狀態管理能力,可根據自己的意愿隨時修改、清除任意上下文信息,譬如很輕易就能實現強制某用戶下線的這樣功能。(來自鳳凰架構)
Cookie-Session 模式的劣勢
在單節點的單體服務中再適合不過,但是如果需要水平擴展要部署集群就很麻煩。
如果讓 session 分配到不同的的節點上,不重復地保存著一部分用戶的狀態,用戶的請求固定分配到對應的節點上,如果某個節點崩潰了,則里面的用戶狀態就會完全丟失。如果讓 session 復制到所有節點上,那么同步的成本又會很高。
而為了解決分布式下的認證授權問題,并順帶解決少量狀態的問題,就有了 JWT 令牌方案,但是 JWT 令牌和 Cookie-Session 并不是完全對等的解決方案,JWT 只能處理認證授權問題,且不能說 JWT 比 Cookie-Session 更加先進,也不可能全面取代 Cookie-Seesion 機制。
JWT 方案
我們上面說到 Cookie-Session 機制在分布式環境下會遇到一致性和同步成本的問題,而且如果在多方系統中,則更不能將 Session 共享存放在多方系統的服務端中,即使服務端之間能共享數據,Cookie 也沒有辦法跨域。
轉換思路,服務端不保存任何狀態信息,由客戶端來存儲,每次發送請求時攜帶這個狀態信息發給后端服務。原理圖如下所示:
但是這種方式無法攜帶大量信息,而且有泄漏和篡改的安全風險。信息量大小受限沒有比較好的解決方案,但是確保信息不被中間人篡改則可以借助 JWT 方案。
JWT(JSON WEB TOKEN)是一種令牌格式,經常與 OAuth2.0 配合應用于分布式、多方系統的應用系統中。
我們先來看下 JWT 的格式長什么樣:
以上截圖來自 JWT 官網(https://jwt.io),數據則是悟空隨意編的。
左邊的字符串就是 JWT 令牌,JWT 令牌是服務端生成的,客戶端會拿著這個 JWT 令牌在每次發送請求時放到 HTTP header 中。
而右邊是 JWT 經過 Base64 解碼后展示的明文內容,而這段明文內容的最下方,又有一個簽名內容,可以防止內容篡改?,但是不能解決泄漏的問題。
JWT 格式
JWT 令牌是以 JSON 結構存儲,用點號分割為三個部分。
第一部分是令牌頭(Header),內容如下所示:
{
"alg": "HS256",
"typ": "JWT"
}
它描述了令牌的類型(統一為 typ:JWT)以及令牌簽名的算法,示例中 HS256 為 HMAC SHA256 算法的縮寫,其他各種系統支持的簽名算法可以參考https://jwt.io/網站所列。
令牌的第二部分是負載(Payload),這是令牌真正需要向服務端傳遞的信息。但是服務端不會直接用這個負載,而是通過加密傳過來的 Header 和 Payload 后再比對簽名是否一致來判斷負載是否被篡改,如果沒有被篡改,才能用 Payload 中的內容。因為負載只是做了 base64 編碼,并不是加密,所以是不安全的,千萬別把敏感信息比如密碼放到負載里面。
{
"sub": "passjava",
"name": "悟空聊架構",
"iat": 1516239022
}
令牌的第三部分是簽名(Signature),使用在對象頭中公開的特定簽名算法,通過特定的密鑰(Secret,由服務器進行保密,不能公開)對前面兩部分內容進行加密計算,以例子里使用的 JWT 默認的 HMAC SHA256 算法為例,將通過以下公式產生簽名值:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload) , secret)
簽名的意義:確保負載中的信息是可信的、沒有被篡改的,也沒有在傳輸過程中丟失任何信息。因為被簽名的內容哪怕發生了一個字節的變動,也會導致整個簽名發生顯著變化。此外,由于簽名這件事情只能由認證授權服務器完成(只有它知道 Secret),任何人都無法在篡改后重新計算出合法的簽名值,所以服務端才能夠完全信任客戶端傳上來的 JWT 中的負載信息。
JWT 的優勢
- 無狀態:不需要服務端保存 JWT 令牌,也就是說不需要服務節點保留任何一點狀態信息,就能在后續的請求中完成認證功能。
- 天然的擴容便利:服務做水平擴容不用考慮 JWT 令牌,而 Cookie-Session 是需要考慮擴容后服務節點如何存儲 Session 的。
- 不依賴 Cookie:JWT 可以存放在瀏覽器的 LocalStorage,不一定非要存儲在 Cookie 中。
JWT 的劣勢
- 令牌難以主動失效:JWT 令牌簽發后,理論上和認證的服務器就沒有什么關系了,到期之前始終有效。除非服務器加些特殊的邏輯處理來緩存 JWT,并來管理 JWT 的生命周期,但是這種方式又會退化成有狀態服務。而這種要求有狀態的需求又很常見:譬如用戶退出后,需要重新輸入用戶名和密碼才能登錄;或者用戶只允許在一臺設備登錄,登錄到另外一臺設備,要求強行退出。但是這種有狀態的模式,降低了 JWT 本身的價值。
- 更容易遭受重放攻擊:Cookie-Session 也有重放攻擊的問題,也就是客戶端可以拿著這個 cookie 不斷發送大量請求,對系統性能造成影響。但是因為 Session 在服務端也有一份,服務端可以控制 session 的生命周期,應對重放攻擊更加主動一些。但是 JWT 的重放攻擊對于服務端來說就很被動,比如通過客戶端的驗證碼、服務端限流或者縮短令牌有效期,應用起來都會麻煩些。
- 存在泄漏的風險:客戶端存儲,很有可能泄漏出去,被其他人重復利用。
- 信息大小有限:HTTP 協議并沒有強制約束 Header 的最大長度,但是服務器、瀏覽器會做限制。而且如果令牌很大還會消耗傳輸帶寬。
真假美猴王
西游記中還有一個章節,假的美猴王帶著通關文牒和其他行李跑到了花果山,還想自行取經,這不就是盜用 JWT 令牌了嗎?
如何使用 JWT
Java 有現成的工具類可以使用,而且校驗 JWT 的工作可以統一交給網關來做,這個就是下一篇要重點講解的實戰內容了。
總結
唐玄奘就好比客戶端,通關文牒就好比 JWT 令牌,經過的每個國家就好比集群中的微服務。
唐玄奘借助 JWT 令牌的認證授權模式,一路通關,最終取得真經,是不是很酷呀~