1. 程式人生 > >JWT- 認證機制

JWT- 認證機制

關於JWT,它是基於json資料結構的認證規範,簡單來說就是驗證使用者登入狀態,跟之前的session很類似,那麼,在有些時候我們為什麼用JWT而不用session呢?

首先我們要了解一下HTTP:
http協議本身是一種無狀態的協議,這就意味著每次請求API的時候,
都會把使用者名稱和密碼傳給服務端,當我們通過http請求傳送給服務端的時候,很有可能將我們的使用者名稱密碼直接暴露給第三方客戶端,風險非常大,因此生產環境下很少用這個方法

session和cookie

這個我們就比較熟悉了,大概的流程就是 使用者第一次訪問伺服器會攜帶使用者的登入
資訊,然後服務端建立session物件,session物件儲存著各種關鍵資訊,同時向客戶端傳送一組sessionid,成為一個cookie物件儲存在瀏覽器中, 當認證是,cookie的資料會傳入服務端與session進行匹配來進行資料認證。

此時,session實現的就是一個有狀態的思想,即改服務的例項可以將一部分資料隨時進行備份,並且建立一個新的狀態服務是通過備份恢復這些資料,達到資料持久化。

缺點:
安全性:首先是cookies的安全性並不是很好,攻擊者可以截獲cookies進行CSRF攻擊;

跨域問題:使用cookies是,如果在多個域名下,就會存在跨域問題

有狀態: session在一定時間裡,需要存放在服務端,因此專案上線之後,如果大量使用者同時訪問,需要實現狀態保持,就會大幅度降低服務端的效能。

狀態問題: 當我們的專案有多個伺服器的時候,如何共享session也是一個問題,如果使用者第一個訪問的伺服器是A,而第二個請求被轉發到了伺服器B,B根本無法得知其狀態。

移動端APP: 手機端並不支援原生cookie,使用會非常麻煩

Token認證

我們這的token是指 訪問資源的憑據,使用基於token的身份認證方法,在
服務端不需要儲存使用者的登入記錄,大概的流程是這樣:
1. 客戶端使用使用者名稱跟密碼請求登入
2. 服務端收到請求,去驗證使用者名稱與密碼
3. 驗證成功後,伺服器端會簽發一個Token,再把這個Token傳送給客戶端
4. 客戶端收到Token後把它儲存起來,比如放在cookie中
5. 客戶端每次向伺服器傳送請求時需要攜帶這個token
6. 伺服器收到請求,然後去驗證客戶端請求裡面帶著的token,如果驗證成功,就向客戶端返回請求的資料

我認為這個Token機制就是加強的session,使其更加簡便安全。

那麼我們具體來說一下它的好處:
1. 支援跨域訪問: 只要你傳輸的使用者認證資訊通過HTTP的headers盡心傳輸
2. 無狀態: Token機制本質是校驗,它得到的回話狀態完全來自於客戶端,token機制在服務端不需要儲存session資訊,因為它自身就包含了所有登入使用者的資訊。
3. 更實用CDN:可以通過內容分發網路請求你的服務端的所有資料(如:
JavaScript, HTML, 圖片),而你的服務端只需提供API
4. 去耦:不需要繫結到一個特定的身份驗證方案,只需對身份資訊進行加密
5. 效能: 一次網路往返時間(通過資料庫查詢session資訊)總比做一次HMACSHA256 計算 的Token驗證和解析要費時得多.不需要為登入頁面做特殊處理: 如果你使用Protractor 做功能測試的時候,不再需要 為登入頁面做特殊處理.
6. 基於標準化:你的API可以採用標準化的 JSON Web Token (JWT). 這個標準已經存在 多個後端庫(.NET, Ruby, Java,Python, PHP)和多家公司的支援(如: Firebase,Google, Microsoft)

說了這麼多Token認證的好處,但它也有不少的缺點:

1.佔頻寬:正常情況下要比 session_id 更大,需要消耗更多流量,擠佔更多頻寬,假如你的網站每月有 10 萬次的瀏覽器,就意味著要多開銷幾十兆的流量。聽起來並不多,但日積月累也是不小一筆開銷。實際上,許多人會在 JWT 中儲存的資訊會更多。
2. 無法在服務端登出,那麼久很難解決劫持問題
3. 效能問題:
JWT 的賣點之一就是加密簽名,由於這個特性,接收方得以驗證 JWT 是否有效且被信任。但是大多數 Web 身份認證應用中,JWT 都會被儲存到 Cookie 中,這就是說你有了兩個層面的簽名。聽著似乎很牛逼,但是沒有任何優勢,為此,你需要花費兩倍的 CPU 開銷來驗證簽名。對於有著嚴格效能要求的 Web 應用,這並不理想,尤其對於單執行緒環境。

JWT的構成

第一部分我們稱它為頭部(header),第二部分我們稱其為載荷(payload, 類似於飛機上承載的物品),第三部分是簽證(signature).

header

jwt的頭部承載兩部分資訊:

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

完整的頭部就像下面這樣的JSON:

{
‘typ’: ‘JWT’,
‘alg’: ‘HS256’
}

然後將頭部進行base64加密(該加密是可以對稱解密的),構成了第一部分.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

payload

載荷就是存放有效資訊的地方。這個名字像是特指飛機上承載的貨品,這些有效資訊包含三個部分

標準中註冊的宣告
公共的宣告
私有的宣告

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

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

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

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

定義一個payload:

{
“sub”: “1234567890”,
“name”: “John Doe”,
“admin”: true
}

然後將其進行base64加密,得到JWT的第二部分。

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

signature

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

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

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

// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);

var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

將這三部分用.連線成一個完整的字串,構成了最終的jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

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