JSON Web Token 是 rfc7519 出的一份標準,使用 JSON 來傳遞數據,用於斷定用戶是否登陸狀態。javascript
jwt 以前,使用 session 來作用戶認證。html
如下代碼均使用 javascript 編寫。
原文連接見 山月的博客前端
傳統登陸的方式是使用 session + token
。java
token
是指在客戶端使用 token 做爲用戶狀態憑證,瀏覽器通常存儲在 localStorage
或者 cookie
中。mysql
session
是指在服務器端使用 redis 或者 sql 類數據庫,存儲 user_id 以及 token 的鍵值對關係,基本工做原理以下。git
const sessions = { "ABCED1": 10086, "CDEFA0": 10010 } // 經過 token 獲取 user_id, 完成認證過程 function getUserIdByToken (token) { return sessions[token] }
若是存儲在 cookie
中就是常常聽到的 session + cookie
的登陸方案。其實存儲在 cookie
,localStorage
甚至 IndexedDB
或者 WebSQL
各有利弊,核心思想一致。github
關於 cookie
以及 token
優缺點,在 token authetication vs cookies 中有討論。web
若是不使用 cookie,能夠採起 localStorage + Authorization
的方式進行認證。redis
// http 的頭,每次請求權限接口時,須要攜帶 Authorization Header const headers = { Authorization: `Bearer ${localStorage.get('token')}` }
推薦一個庫 localForage,使用IndexedDB
,WebSQL
以及IndexedDB
作鍵值對存儲。
session
須要在數據庫中保持用戶及token對應信息,因此叫 有狀態。算法
試想一下,如何在數據庫中不保持用戶狀態也能夠登陸。
第一種方法: 前端直接傳 user_id 給服務端
缺點也特別特別明顯,容易被用戶篡改爲任務 user_id,權限設置形同虛設。不過思路正確,接着往下走。
改進: 對 user_id 進行對稱加密
比上邊略微強點,若是說上一種方法是空窗戶,這種方法就是糊了紙的窗戶。
改進: 對 user_id 不須要加密,只須要進行簽名,保證不被篡改
這即是 jwt 的思想,user_id,加密算法和簽名一塊兒存儲到客戶端,每次請求接口時,服務器判斷簽名是否一致。
jwt 由 Header
,Payload
以及 Signature
由 .
拼接而成。
Header 由非對稱加密算法和類型組成,以下
const header = { // 加密算法 alg: 'HS256', type: 'jwt' }
Payload 中由 Registered Claim 以及須要通訊的數據組成。這些數據字段也叫 Claim
。
Registered Claim
中比較重要的是 "exp" Claim
表示過時時間,在用戶登陸時會設置過時時間。
const payload = { // 表示 jwt 建立時間 iat: 1532135735, // 表示 jwt 過時時間 exp: 1532136735, // 用戶 id,用以通訊 user_id: 10086 }
Sign 由 Header
,Payload
以及 secretOrPrivateKey
計算而成。
對於 secretOrPrivateKey
,若是加密算法採用 HMAC
,則爲字符串,若是採用 RSA
或者 ECDSA
,則爲 PrivateKey。
// 由 HMACSHA256 算法進行簽名,secret 不能外泄 const sign = HMACSHA256(base64.encode(header) + '.' + base64.encode(payload), secret) // jwt 由三部分拼接而成 const jwt = base64.encode(header) + '.' + base64.encode(payload) + '.' + sign
從生成 jwt 規則可知客戶端能夠解析出 payload,所以不要在 payload 中攜帶敏感數據,好比用戶密碼
在生成規則中可知,jwt 前兩部分是對 header 以及 payload 的 base64 編碼。
當服務器收到客戶端的 token 後,解析前兩部分獲得 header 以及 payload,並使用 header 中的算法與 secretOrPrivateKey 進行簽名,判斷與 jwt 中的簽名是否一致。
如何判斷 token 過時?
由上可知,jwt 並不對數據進行加密,而是對數據進行簽名,保證不被篡改。除了在登陸中能夠用到,在進行郵箱校驗和圖形驗證碼也能夠用到。
在登陸時,輸入密碼錯誤次數過多會出現圖形驗證碼。
圖形驗證碼的原理是給客戶端一個圖形,而且在服務器端保存與這個圖片配對的字符串,之前也大都經過 session 來實現。
能夠把驗證碼配對的字符串做爲 secret,進行無狀態校驗。
const jwt = require('jsonwebtoken') // 假設驗證碼爲字符驗證碼,字符爲 ACDE,10分鐘失效 const token = jwt.sign({ userId: 10085 }, secrect + 'ACDE', { expiresIn: 60 * 10 })
如今網站在註冊成功後會進行郵箱校驗,具體作法是給郵箱發一個連接,用戶點開連接校驗成功。
// 把郵箱以及用戶id綁定在一塊兒 const code = jwt.sign({ email, userId }, secret, { expiresIn: 60 * 30 }) // 在此連接校驗驗證碼 const link = `https://example.com/code=${code}`
關於無狀態和有狀態,在其它技術方向也有對比,好比 React 的 stateLess component 以及 stateful component,函數式編程中的反作用能夠理解爲狀態,http 也是一個無狀態協議,須要靠 header 以及 cookie 攜帶狀態。
在用戶認證這裏,有無狀態是指是否依賴外部數據存儲,如 mysql,redis 等。
思考如下幾個關於登陸的問題如何使用 session 以及 jwt 實現
由於 jwt 無狀態,不保存用戶設備信息,無法單純使用它完成以上問題,能夠再利用數據庫保存一些狀態完成。
對於這個需求,session 稍微簡單些,畢竟 jwt 也須要依賴數據庫。
對於這個需求,jwt 略簡單些,而使用 session 還須要多維護一張 token 表。
從以上問題得知,若是不須要控制登陸設備數量以及設備信息,無狀態的 jwt 是一個不錯的選擇。一旦涉及到了設備信息,就須要對 jwt 添加額外的狀態支持,增長了認證的複雜度,此時選用 session 是一個不錯的選擇。
jwt 不是萬能的,是否採用 jwt,須要根據業務需求來肯定。