基於Token的身份驗證——JSON Web Token
Token的維基百科
- token的維基百科,可以瞭解Token的發展和定義。
Token的廣泛應用
- 現如今的很多大型網站,比如Facebook、Twitter、Google+以及Github等等,比起傳統的身份驗證方法,Token擴展性更強,也更安全點,非常適合用在Web應用或者移動應用上。
- Token的在中文翻譯中有”令牌”之意,意思就是,你只有拿着令牌,才能打開一道通往新世界的大門。
傳統身份驗證的方法
- HTTP是一種沒有狀態的協議,也就是它並不知道是誰訪問應用。這裏可以把用戶看作是客戶端,客戶端使用用戶名(username)和密碼(password)通過了身份驗證,但是這種方式再下次這個客戶端再發送請求的時候,還需要再次驗證一下。
- 解決的方法就是,當用戶請求登錄的時候,如果沒有問題(正確的用戶名和密碼),會在服務器端生成一條記錄,這條記錄可以說明一下登錄的用戶是誰,然後是把這條記錄的ID號發送給客戶端,客戶端接收到這條記錄的ID號之後把這個ID號存儲在Cookie裏,下次這個用戶再向服務器端發送請求的時候,可以攜帶這個Cookie,這樣在服務器端會驗證一下這個Cookie裏的信息,看看能不能在服務器端這裏找到對應的記錄,如果可以,說明用戶已經通過了身份驗證,就把用戶請求的數據返回給客戶端了。
- 以上所述的就是Session,需要在服務器端存儲爲登錄的用戶生成的Session,這些Session可能會存儲在內存,磁盤,或者數據庫裏。同時也可能需要在服務器端定期的去清理過期的Session。
基於Token的身份驗證方法
- 使用基於Token的身份驗證方法,在服務器端不需要存儲用戶的登錄記錄。大概的流程是這樣的:
- 1、客戶端使用用戶名跟密碼進行請求登錄
- 2、服務端收到請求,去驗證用戶名和密碼
- 3、驗證成功後,服務端會簽發一個Token,再把這個Token發送給客戶端
- 4、客戶端收到Token以後可以把它存儲起來,比如放在Cookie裏或者Local Storage裏
- 5、客戶端每次向服務端請求資源的時候需要帶着服務端簽發的Token
- 6、服務端收到請求,然後去驗證客戶端請求裏面帶着的Token,如果驗證成功,就向客戶端返回請求的數據
JWT
- 實施Token驗證的方法挺多的,還有一些標準方法,比如JWT,讀作:jot,表示:JSON Web Tokens。JWT 標準的Token有三個部分:
- header(頭部)
- payload(數據)
- signature(簽名)
- 中間用點分隔開,並且都會使用Base64編碼,所以真正的Token看起來像這樣:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSmFja0RhbiIsImFkbWluIjp0cnVlLCJpYXQiOjE1MzYxMzQxMzcsImV4cCI6NDY5ODM3NDEzN30.yuGHsgfJbg5ArbeVGKJENQOYuBsYFFLDbwiExkPSH_k
Header
- 每個JWT token 裏面都有一個header,也就是頭部數據。裏面包含了使用的算法,這個JWT是不是帶標籤名或者加密的。主要就是說明一下怎麼處理這個JWT token。
- 頭部裏包含的東西可能會根據JWT的類型有所變化,比如一個加密的JWT裏面包含使用的加密的算法。唯一在頭部裏面要包含的是alg 這個屬性,如果是加密的JWT,這個屬性的值就是使用的簽名或者解密的算法。如果是未加密的JWT,這個屬性就設置爲none。
- 示例:
{
"alg": "HS256"
}
- 意思是這個JWT用的算法是HS256。上面的內容得用base64url的形式編碼一下,所以就變成這樣:
eyJuYW1lIjoiSmFja0RhbiIsImFkbWluIjp0cnVlLCJpYXQiOjE1MzYxMzQxMzcsImV4cCI6NDY5ODM3NDEzN30
Payload
- Payload裏面是Token的具體內容,這些內容裏面有一些是標準字段,也可以添加其他需要的內容。下面是標準字段:
- iss:Issuer,發行者
- sub:Subject,主題
- aud:Audience,觀衆
- exp:Expiration time,過期時間
- nbf:Not before
- iat:Issued at,發行時間
- jti:JWT ID
- 比如下面這個Payload,用到了iss 發行人,還有exp 過期時間這兩個標準字段。另外還有兩個自定義的字段,一個是name,還有一個是admin。
{
"iss": "fengjun.com",
"exp": "145637890",
"name": "JackDan",
"admin": true
}
- 使用base64url編碼以後變成了這個樣子:
eyJuYW1lIjoiSmFja0RhbiIsImFkbWluIjp0cnVlLCJpYXQiOjE1MzYxMzQxMzcsImV4cCI6NDY5ODM3NDEzN30
Signature
- JWT的最後一部分是Singature,這部分內容有三個部分,先是用Base64編碼的header.payload,再用加密算法加密一下,加密的時候要放進去一個Secret,這個相當於是一個密碼,這個密碼祕密地存儲在服務端。
- header
- payload
- secret
const encodedString = base64UrlEncode(header) + "." + base64UrlEncode(payload);
HMACSHA256(encodedString, 'secret');
- 處理完成之後看起來像這樣:
yuGHsgfJbg5ArbeVGKJENQOYuBsYFFLDbwiExkPSH_k
- 最後這個在服務端生成並且要發送給客戶端的Token看起來像這樣:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSmFja0RhbiIsImFkbWluIjp0cnVlLCJpYXQiOjE1MzYxMzQxMzcsImV4cCI6NDY5ODM3NDEzN30.yuGHsgfJbg5ArbeVGKJENQOYuBsYFFLDbwiExkPSH_k
- 客戶端收到這個Token以後把它存儲下來,下回向服務端發送請求的時候就帶着這個Token。服務端收到這個Token,然後進行驗證,通過以後就會返回給客戶端想要的資源。
代碼驗證——簽發和驗證JWT
- 在應用裏實施使用基於JWT這種Token的身份驗證方法,你可以先去找一個簽發與驗證JWT的功能包。無論你的後端應用使用的是什麼樣的程序語言,系統,或者框架,你應該都可以找到提供類似功能的包。
- 這裏採用Node.js來進行代碼驗證。
準備項目
- 準備一個簡單的Node.js項目
cd testWorkspace
mkdir jwt_demo
cd jwt_demo
npm init -y
- 效果圖如下:
安裝簽發與驗證JWT的功能包,這裏使用的是jsonwebtoken,在項目裏面安裝這個包。
效果圖如下:
package.json
效果圖:
簽發JWT
- 在項目裏隨便添加一個
.js
文件,比如index.js
,在文件裏添加下面這些代碼:
const jwt = require('jsonwebtoken')
// token data token數據
const payload = {
name: 'JackDan',
admin: true
}
// secret 密鑰
const secret = 'JUNJUNLOVEFENGFENG'
// 簽發 token
const token = jwt.sign(payload, secret, {expiresIn: '36600days'})
// 輸出簽發的 Token
console.log(token)
- 非常簡單,就是用了剛剛爲項目安裝的
jsonwebtoken
裏面提供的jwt.sign
功能,去簽發一個token。這個sign方法需要三個參數:
- 1、payload: 簽發的token裏面要包含的一些數據。
- 2、secret: 簽發token用的密鑰,在驗證token的時候同樣需要用到這個密鑰。
- 3、options: 一些其他的選項。
- 在命令行下面,用node命令,執行一下項目裏的
index.js
這個文件(node index.js),會輸出應用簽發的token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSmFja0RhbiIsImFkbWluIjp0cnVlLCJpYXQiOjE1MzYxMzQxMzcsImV4cCI6NDY5ODM3NDEzN30.yuGHsgfJbg5ArbeVGKJENQOYuBsYFFLDbwiExkPSH_k
- 上面的Token內容並沒有加密,所以如果用一些JWT解碼功能,可以看到Token裏面包含的內容,內容由三個部分組成,像這樣:
// header
{
"alg": "HS256",
"typ": "JWT"
}
// payload
{
name: 'JackDan',
admin: true,
iat: 1536134993,
exp: 4698374993
}
// signature
yuGHsgfJbg5ArbeVGKJENQOYuBsYFFLDbwiExkPSH_k
- 假設用戶通過某種身份驗證,你就可以使用上面簽發的Token的功能爲用戶簽發一個Token。一般在客戶端那裏會把它保存在Cookie或者LocalStorage裏面。
- 用戶下次向我們的應用請求受保護資源的時候,可以在請求裏帶着我們給它簽發的這個Token,後端應用收到請求,檢查簽名,如果驗證通過確定這個Token是我們自己簽發的,那就可以爲用戶響應回他需要的資源。
驗證JWT
- 驗證JWT的有效性,確定一下用戶的JWT是我們自己簽發的,首先要得到用戶的這個JWT Token,然後用
jwt.verify
這個方法去做一下驗證。這個方法是Node.js的jsonwebtoken這個包提供的,在其他的應用框架或者系統裏,你可能會找到類似的方法來驗證JWT。 - 打開項目中的index.js文件,添加如下代碼:
// 驗證 Token
jwt.verify(token, secret, (error, decoded) => {
if (error) {
console.log(error.message)
return
}
console.log(decoded)
})
- 把要驗證的Token數據,還有簽發這個Token的時候用的那個密鑰告訴verify這個方法,在一個回調裏面有兩個參數,error表示錯誤,decoded是解碼之後的Token數據。
- 執行:
C:\projects\testWorkspace\jwt_demo>node index.js
- 輸出數據:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSmFja0RhbiIsImFkbWluIjp0cnVlLCJpYXQiOjE1MzYxMzQ5OTMsImV4cCI6NDY5ODM3NDk5M30.ELAlzPGVvjsK0kK1Yl1PArb0wS3860R6c7mqG-5M4eY
{ name: 'JackDan', admin: true, iat: 1536134993, exp: 4698374993 }
RS256算法
- 默認簽發還有驗證Token的時候用的是HS256算法,這種算法需要一個密鑰(密碼)。我們還可以使用RS256算法簽發與驗證JWT。這種方法可以讓我們分離開簽發與驗證,簽發時需要用一個密鑰,驗證時使用公鑰,也就是有公鑰的地方可以做驗證,但是不能做簽發。
- 在項目下面創建一個新的目錄,裏面可以存儲即可將生成的密鑰與公鑰文件。
C:\projects\testWorkspace\jwt_demo>mkdir config
C:\projects\testWorkspace\jwt_demo>cd config
- 密鑰
- 先生成一個密鑰:
C:\projects\testWorkspace\jwt_demo\config>ssh-keygen -t rsa -b 2048 -f private.key
- 公鑰
- 基於上面生成的密鑰,再去創建一個對應的公鑰:
C:\projects\testWorkspace\jwt_demo\config>openssl rsa -in private.key -pubout -outform PEM -out public.key
- 效果圖如下:
簽發JWT(RS256算法)
- 用RS256算法簽發JWT的時候,需要從文件系統上讀取創建的密鑰文件裏的內容。
const fs = require('fs')
// 獲取簽發 JWT 時需要用的密鑰
const privateKey = fs.readFileSync('./config/private.key')
- 效果圖:
- 簽發仍然使用
jwt.sign
方法,只不過在選項參數裏特別說明一下使用的算法是RS256:
// 獲取驗證 JWT 時需要用的公鑰
const publickey = fs.readFileSync('./config/public.key')
// 驗證 Token
jwt.verify(tokenRS256, publickey, (error, decoded) => {
if (error) {
console.log(error.message)
return
}
console.log(decoded)
})
- 效果圖:
代碼地址
- jwt_demo,個人驗證代碼的github地址。
JackDan Thinking