1. 程式人生 > 實用技巧 >JWT詳解

JWT詳解

一、什麼是JWT

JWT(JSON Web Token) 是一個開放標準(RFC 7519),它定義了一種緊湊的、自包含的方式,用於作為JSON物件在各方之間安全地傳輸資訊。該資訊可以被驗證和信任,因為它是數字簽名的。

二、使用場景(來自理解JWT的使用場景和優劣

  • 一次性驗證:
    比如使用者註冊後需要發一封郵件讓其啟用賬戶,通常郵件中需要有一個連結,這個連結需要具備以下的特性:能夠標識使用者,該連結具有時效性(通常只允許幾小時之內啟用),不能被篡改以啟用其他可能的賬戶…這種場景就和 jwt 的特性非常貼近,jwt 的 payload 中固定的引數:iss 簽發者和 exp 過期時間正是為其做準備的。
  • restful api的無狀態認證
    使用 jwt 來做 restful api 的身份認證也是值得推崇的一種使用方案。客戶端和服務端共享 secret;過期時間由服務端校驗,客戶端定時重新整理;簽名信息不可被修改。spring security oauth jwt 提供了一套完整的 jwt 認證體系。

三、JWT結構

JWT由三部分組成,它們之間用圓點(.)連線。這三部分分別是:

  • Header
  • Payload
  • Signature

具體示例如下所示:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

3.1 Header

jwt的頭部由兩部分資訊組成:

  • type:宣告型別,這裡是jwt
  • alg:宣告加密的演算法 通常直接使用 HMAC SHA256

完整的頭部資訊如下:

{
  "type":"jwt",
  "alg":"HS256"
}

對頭部資訊進行Base64編碼的得到第一部分的資訊

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

3.2 Payload

載荷就是存放有效資訊的地方,它包含宣告(要求)。宣告有三種類型:

  • registered claims:標準中註冊的宣告。這裡有一組預定義的宣告,它們不是強制的,但是推薦
  • public claims:公共的宣告
  • private claims:私有的宣告

標準中註冊的宣告 (建議但不強制使用) :

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

公共的宣告 :

公共的宣告可以新增任何的資訊,一般新增使用者的相關資訊或其他業務需要的必要資訊.但不建議新增敏感資訊,因為該部分在客戶端可解密.

私有的宣告 :

私有宣告是提供者和消費者所共同定義的宣告,一般不建議存放敏感資訊,因為base64是對稱解密的,意味著該部分資訊可以歸類為明文資訊。

對Payload進行Base64加密就得到了JWT第二部分的內容。

3.3 signature

JWT的第三部分是一個簽證資訊,這個簽證資訊由三部分組成:

  • header (base64後的)
  • payload (base64後的)
  • secret

第三部分需要base64加密後的header和base64加密後的payload使用 . 連線組成的字串,然後通過header中宣告的加密方式進行加鹽secret組合加密,然後就構成了JWT的第三部分。

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

四、JWT,OAuth2,Session對比

4.1 傳統的session認證

http協議本身是一種無狀態的協議,而這就意味著如果使用者向我們的應用提供了使用者名稱和密碼來進行使用者認證,那麼下一次請求時,使用者還要再一次進行使用者認證才行,因為根據http協議,我們並不能知道是哪個使用者發出的請求,所以為了讓我們的應用能識別是哪個使用者發出的請求,我們只能在伺服器儲存一份使用者登入的資訊,這份登入資訊會在響應時傳遞給瀏覽器,告訴其儲存為cookie,以便下次請求時傳送給我們的應用,這樣我們的應用就能識別請求來自哪個使用者了,這就是傳統的基於session認證。

但是這種基於session的認證使應用本身很難得到擴充套件,隨著不同客戶端使用者的增加,獨立的伺服器已無法承載更多的使用者,而這時候基於session認證應用的問題就會暴露出來:

  • Session: 每個使用者經過我們的應用認證之後,我們的應用都要在服務端做一次記錄,以方便使用者下次請求的鑑別,通常而言session都是儲存在記憶體中,而隨著認證使用者的增多,服務端的開銷會明顯增大
  • 擴充套件性: 使用者認證之後,服務端做認證記錄,如果認證的記錄被儲存在記憶體中的話,這意味著使用者下次請求還必須要請求在這臺伺服器上,這樣才能拿到授權的資源,這樣在分散式的應用上,相應的限制了負載均衡器的能力。這也意味著限制了應用的擴充套件能力
  • CSRF: 因為是基於cookie來進行使用者識別的, cookie如果被截獲,使用者就會很容易受到跨站請求偽造的攻擊

4.2 基於token的鑑權機制

JWT和OAuth2都是基於token的鑑權機制。基於token的鑑權機制類似於http協議也是無狀態的,它不需要在服務端去保留使用者的認證資訊或者會話資訊。這就意味著基於token認證機制的應用不需要去考慮使用者在哪一臺伺服器登入了,這就為應用的擴充套件提供了便利。

其基本的流程如下:

  1. 使用者使用使用者名稱密碼來請求伺服器
  2. 伺服器進行驗證使用者的資訊
  3. 伺服器通過驗證傳送給使用者一個token
  4. 客戶端儲存token,並在每次請求時附送上這個token值
  5. 服務端驗證token值,並返回資料

這個token必須要在每次請求時傳遞給服務端,它應該儲存在請求頭裡, 另外,服務端要支援CORS(跨來源資源共享)策略,一般我們在服務端這麼做就可以了Access-Control-Allow-Origin: *

4.3 JWT 認證協議與 OAuth2.0 授權框架不恰當比較(來自基於 Token 的 JWT 認證協議與 OAuth2.0 授權框架不恰當比較

之所以說是不恰當,是因為JWT和OAuth2是完全不通過的概念。既然 JWT 和 OAuth2 沒有可比性,為什麼還要把這兩個放在一起說呢?很多情況下,在討論OAuth2的實現時,會把JSON Web Token作為一種認證機制使用。這也是為什麼他們會經常一起出現。

  1. JWT 是一種認證協議
    JWT提供了一種用於釋出接入令牌(Access Token),並對釋出的簽名接入令牌進行驗證的方法。 令牌(Token)本身包含了一系列宣告,應用程式可以根據這些宣告限制使用者對資源的訪問。
  2. OAuth2 是一種授權框架
    OAuth2是一種授權框架,提供了一套詳細的授權機制。使用者或應用可以通過公開的或私有的設定,授權第三方應用訪問特定資源。
  3. JWT 使用場景
    JWT 的主要優勢在於使用無狀態、可擴充套件的方式處理應用中的使用者會話。服務端可以通過內嵌的宣告資訊,很容易地獲取使用者的會話資訊,而不需要去訪問使用者或會話的資料庫。在一個分散式的面向服務的框架中,這一點非常有用。但是,如果系統中需要使用黑名單實現長期有效的 Token 重新整理機制,這種無狀態的優勢就不明顯了。
  • 優勢
    • 快速開發
    • 不需要 Cookie
    • JSON 在移動端的廣泛應用
    • 不依賴於社交登入
    • 相對簡單的概念理解
  • 限制
    • Token有長度限制
    • Token不能撤銷
    • 需要 Token 有失效時間限制(exp)
  1. OAuth2 使用場景
    如果不介意API的使用依賴於外部的第三方認證提供者,你可以簡單地把認證工作留給認證服務商去做。也就是常見的,去認證服務商(比如 Facebook)那裡註冊你的應用,然後設定需要訪問的使用者資訊,比如電子郵箱、姓名等。當用戶訪問站點的註冊頁面時,會看到連線到第三方提供商的入口。使用者點選以後被重定向到對應的認證服務商網站,獲得使用者的授權後就可以訪問到需要的資訊,然後重定向回來。
  • 優勢
    • 快速開發
    • 實施程式碼量小
    • 維護工作減少
    • 可以和 JWT 同時使用
    • 可針對不同應用擴充套件
  • 限制
    • 框架沉重



連結:https://www.jianshu.com/p/4a124a10fcaf

摘要:

  • 在Web應用中,使用JWT替代session並不是個好主意
  • 適合JWT的使用場景

抱歉,當了回標題黨。我並不否認JWT的價值,只是它經常被誤用。

什麼是JWT

根據維基百科的定義,JSON WEB Token(JWT,讀作 [/dʒɒt/]),是一種基於JSON的、用於在網路上宣告某種主張的令牌(token)。JWT通常由三部分組成: 頭資訊(header), 訊息體(payload)和簽名(signature)。

頭資訊指定了該JWT使用的簽名演算法:

  1. header = '{"alg":"HS256","typ":"JWT"}'

HS256表示使用了 HMAC-SHA256 來生成簽名。

訊息體包含了JWT的意圖:

  1. payload = '{"loggedInAs":"admin","iat":1422779638}'//iat表示令牌生成的時間

未簽名的令牌由base64url編碼的頭資訊和訊息體拼接而成(使用"."分隔),簽名則通過私有的key計算而成:

  1. key = 'secretkey'
  2. unsignedToken = encodeBase64(header) + '.' + encodeBase64(payload)
  3. signature = HMAC-SHA256(key, unsignedToken)

最後在未簽名的令牌尾部拼接上base64url編碼的簽名(同樣使用"."分隔)就是JWT了:

  1. token = encodeBase64(header) + '.' + encodeBase64(payload) + '.' + encodeBase64(signature)
  2. # token看起來像這樣: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI

JWT常常被用作保護服務端的資源(resource),客戶端通常將JWT通過HTTP的Authorizationheader傳送給服務端,服務端使用自己儲存的key計算、驗證簽名以判斷該JWT是否可信:

  1. Authorization: Bearer eyJhbGci*...<snip>...*yu5CSpyHI

那怎麼就誤用了呢

近年來RESTful API開始風靡,使用HTTP header來傳遞認證令牌似乎變得理所應當,而單頁應用(SPA)、前後端分離架構似乎正在促成越來越多的WEB應用放棄歷史悠久的cookie-session認證機制,轉而使用JWT來管理使用者session。支援該方案的人認為:

1.該方案更易於水平擴充套件

在cookie-session方案中,cookie內僅包含一個session識別符號,而諸如使用者資訊、授權列表等都儲存在服務端的session中。如果把session中的認證資訊都儲存在JWT中,在服務端就沒有session存在的必要了。當服務端水平擴充套件的時候,就不用處理session複製(session replication)/ session黏連(sticky session)或是引入外部session儲存了。

從這個角度來說,這個優點確實存在,但實際上外部session儲存方案已經非常成熟了(比如Redis),在一些Framework的幫助下(比如spring-sessionhazelcast),session複製也並沒有想象中的麻煩。所以除非你的應用訪問量非常非常非常(此處省略N個非常)大,使用cookie-session配合外部session儲存完全夠用了。

2.該方案可防護CSRF攻擊

跨站請求偽造Cross-site request forgery(簡稱CSRF, 讀作 [sea-surf])是一種典型的利用cookie-session漏洞的攻擊,這裡借用spring-security的一個例子來解釋CSRF:

假設你經常使用bank.example.com進行網上轉賬,在你提交轉賬請求時bank.example.com的前端程式碼會提交一個HTTP請求:

  1. POST /transfer HTTP/1.1
  2. Host: bank.example.com
  3. cookie: JsessionID=randomid; Domain=bank.example.com; Secure; HttpOnly
  4. Content-Type: application/x-www-form-urlencoded
  5. amount=100.00&routingNumber=1234&account=9876

你圖方便沒有登出bank.example.com,隨後又訪問了一個惡意網站,該網站的HTML頁面包含了這樣一個表單:

  1. <form action="https://bank.example.com/transfer" method="post">
  2. <input type="hidden" name="amount" value="100.00"/>
  3. <input type="hidden" name="routingNumber" value="evilsRoutingNumber"/>
  4. <input type="hidden" name="account" value="evilsAccountNumber"/>
  5. <input type="submit" value="點選就送!"/>
  6. </form>

你被“點選就送”吸引了,當你點了提交按鈕時你已經向攻擊者的賬號轉了100元。現實中的攻擊可能更隱蔽,惡意網站的頁面可能使用Javascript自動完成提交。儘管惡意網站沒有辦法盜取你的session cookie(從而假冒你的身份),但惡意網站向bank.example.com發起請求時,你的cookie會被自動傳送過去。

因此,有些人認為前端程式碼將JWT通過HTTP header傳送給服務端(而不是通過cookie自動傳送)可以有效防護CSRF。在這種方案中,服務端程式碼在完成認證後,會在HTTP response的header中返回JWT,前端程式碼將該JWT存放到Local Storage裡待用,或是服務端直接在cookie中儲存HttpOnly=false的JWT。

在向服務端發起請求時,用Javascript取出JWT(否則前端Javascript程式碼無權從cookie中獲取資料),再通過header傳送回服務端通過認證。由於惡意網站的程式碼無法獲取bank.example.com的cookie/Local Storage中的JWT,這種方式確實能防護CSRF,但將JWT儲存在cookie/Local Storage中可能會給另一種攻擊可乘之機,我們一會詳細討論它:跨站指令碼攻擊——XSS。

3.該方案更安全

由於JWT要求有一個祕鑰,還有一個演算法,生成的令牌看上去不可讀,不少人誤認為該令牌是被加密的。但實際上祕鑰和演算法是用來生成簽名的,令牌本身不可讀僅是因為base64url編碼,可以直接解碼,所以如果JWT中如果儲存了敏感的資訊,相對cookie-session將資料放在服務端來說,更不安全。

除了以上這些誤解外,使用JWT管理session還有如下缺點:

  1. 更多的空間佔用。如果將原存在服務端session中的各類資訊都放在JWT中儲存在客戶端,可能造成JWT佔用的空間變大,需要考慮cookie的空間限制等因素,如果放在Local Storage,則可能受到XSS攻擊。

  2. 更不安全。這裡是特指將JWT儲存在Local Storage中,然後使用Javascript取出後作為HTTP header傳送給服務端的方案。在Local Storage中儲存敏感資訊並不安全,容易受到跨站指令碼攻擊,跨站指令碼(Cross site script,簡稱xss)是一種“HTML注入”,由於攻擊的指令碼多數時候是跨域的,所以稱之為“跨域指令碼”,這些指令碼程式碼可以盜取cookie或是Local Storage中的資料。可以從這篇文章檢視XSS攻擊的原理解釋。

  3. 無法作廢已頒佈的令牌。所有的認證資訊都在JWT中,由於在服務端沒有狀態,即使你知道了某個JWT被盜取了,你也沒有辦法將其作廢。在JWT過期之前(你絕對應該設定過期時間),你無能為力。

  4. 不易應對資料過期。與上一條類似,JWT有點類似快取,由於無法作廢已頒佈的令牌,在其過期前,你只能忍受“過期”的資料。

看到這裡後,你可能發現,將JWT儲存在Local Storage中,並使用JWT來管理session並不是一個好主意,那有沒有可能“正確”地使用JWT來管理session呢?比如:

  • 不再使用Local Storage儲存JWT,使用cookie,並且設定HttpOnly=true,這意味著只能由服務端儲存以及通過自動回傳的cookie取得JWT,以便防禦XSS攻擊
  • 在JWT的內容中加入一個隨機值作為CSRF令牌,由服務端將該CSRF令牌也儲存在cookie中,但設定HttpOnly=false,這樣前端Javascript程式碼就可以取得該CSRF令牌,並在請求API時作為HTTP header傳回。服務端在認證時,從JWT中取出CSRF令牌與header中獲得CSRF令牌比較,從而實現對CSRF攻擊的防護
  • 考慮到cookie的空間限制(大約4k左右),在JWT中儘可能只放“夠用”的認證資訊,其他資訊放在資料庫,需要時再獲取,同時也解決之前提到的資料過期問題

這個方案看上去是挺不錯的,恭喜你,你重新發明了cookie-session,可能實現還不一定有現有的好。

那究竟JWT可以用來做什麼

我的同事做過一個形象的解釋:

JWT(其實還有SAML)最適合的應用場景就是“開票”,或者“簽字”。

在有紙化辦公時代,多部門、多組織之間的協同工作往往會需要拿著A部門領導的“簽字”或者“蓋章”去B部門“使用”或者“訪問”對應的資源,其實這種“領導簽字/蓋章”就是JWT,都是一種由具有一定權力的實體“簽發”並“授權”的“票據”。一般的,這種票據具有可驗證性(領導簽名/蓋章可以被驗證,且難於模仿),不可篡改性(塗改過的檔案不被接受,除非在塗改處再次簽字確認);並且這種票據一般都是“一次性”使用的,在訪問到對應的資源後,該票據一般會被資源持有方收回留底,用於後續的審計、追溯等用途。

舉兩個例子:

  1. 員工李雷需要請假一天,於是填寫請假申請單,李雷在獲得其主管部門領導簽字後,將請假單交給HR部門韓梅梅,韓梅梅確認領導簽字無誤後,將請假單收回,並在公司考勤表中做相應記錄。
  2. 員工李雷和韓梅梅因工外出需要使用公司汽車一天,於是填寫用車申請單,簽字後李雷將申請單交給車隊司機老王,乘坐老王駕駛的車輛外出辦事,同時老王將用車申請單收回並存檔。

在以上的兩個例子中,“請假申請單”和“用車申請單”就是JWT中的payload,領導簽字就是base64後的數字簽名,領導是issuer,“HR部門的韓梅梅”和“司機老王”即為JWT的audience,audience需要驗證領導簽名是否合法,驗證合法後根據payload中請求的資源給予相應的許可權,同時將JWT收回。

放到系統整合的場景中,JWT更適合一次性操作的認證:

服務B你好, 服務A告訴我,我可以操作<JWT內容>, 這是我的憑證(即JWT)

在這裡,服務A負責認證使用者身份(相當於上例中領導批准請假),並頒佈一個很短過期時間的JWT給瀏覽器(相當於上例中的請假單),瀏覽器(相當於上例中的請假員工)在向服務B的請求中帶上該JWT,則服務B(相當於上例中的HR員工)可以通過驗證該JWT來判斷使用者是否有權執行該操作。這樣,服務B就成為一個安全的無狀態的服務了。

總結

  1. 在Web應用中,別再把JWT當做session使用,絕大多數情況下,傳統的cookie-session機制工作得更好
  2. JWT適合一次性的命令認證,頒發一個有效期極短的JWT,即使暴露了危險也很小,由於每次操作都會生成新的JWT,因此也沒必要儲存JWT,真正實現無狀態。

轉載於:https://www.cnblogs.com/guiyishanren/p/11205931.html