基於 Token 的身份驗證:JSON Web Token(附:Node.js 項目)


最近了解下基於 Token 的身份驗證,跟大伙分享下。很多大型網站也都在用,比如 Facebook,Twitter,Google+,Github 等等,比起傳統的身份驗證方法,Token 擴展性更強,也更安全點,非常適合用在 Web 應用或者移動應用上。Token 的中文有人翻譯成 “令牌”,我覺得挺好,意思就是,你拿着這個令牌,才能過一些關卡。

文章先介紹了一下傳統身份驗證與基於 JWT 身份驗證的方法,再理解一下 JWT 的 Token 的組成部分(頭部,數據,簽名),最后我們會在一個 Node.js 項目上實施簽發與驗證 JWT 的功能。練習的視頻版本可以參考《JWT:JSON Web Token》這個免費的課程,項目代碼在 Github 上可以找到。

寧皓網有一系列的基於 Token 身份驗證的課程,比如在 Node.js 項目里,或者 WordPress 網站上實現這種身份驗證的方法,我們還介紹了在小程序里面使用了這種基於 Token 的方法來驗證小程序用戶的身份。

訂閱寧皓網以后,就可以在線學習所有這些基於 Token 驗證身份的相關課程。

傳統身份驗證的方法

HTTP 是一種沒有狀態的協議,也就是它並不知道是誰是訪問應用。這里我們把用戶看成是客戶端,客戶端使用用戶名還有密碼通過了身份驗證,不過下回這個客戶端再發送請求時候,還得再驗證一下。

解決的方法就是,當用戶請求登錄的時候,如果沒有問題,我們在服務端生成一條記錄,這個記錄里可以說明一下登錄的用戶是誰,然后把這條記錄的 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 看起來像這樣:

eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc

Header

每個 JWT token 里面都有一個 header,也就是頭部數據。里面包含了使用的算法,這個 JWT 是不是帶簽名的或者加密的。主要就是說明一下怎么處理這個 JWT token 。

頭部里包含的東西可能會根據 JWT 的類型有所變化,比如一個加密的 JWT 里面要包含使用的加密的算法。唯一在頭部里面要包含的是 alg 這個屬性,如果是加密的 JWT,這個屬性的值就是使用的簽名或者解密用的算法。如果是未加密的 JWT,這個屬性的值要設置成 none

示例:

{
  "alg": "HS256"
}

 

意思是這個 JWT 用的算法是 HS256。上面的內容得用 base64url 的形式編碼一下,所以就變成這樣:

eyJhbGciOiJIUzI1NiJ9

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": "ninghao.net",
 "exp": "1438955445",
 "name": "wanghao",
 "admin": true
}

 

使用 base64url 編碼以后就變成了這個樣子:

eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ

Signature

JWT 的最后一部分是 Signature ,這部分內容有三個部分,先是用 Base64 編碼的 header.payload ,再用加密算法加密一下,加密的時候要放進去一個 Secret ,這個相當於是一個密碼,這個密碼秘密地存儲在服務端。

  • header
  • payload
  • secret
const encodedString = base64UrlEncode(header) + "." + base64UrlEncode(payload); 
HMACSHA256(encodedString, 'secret');

 

處理完成以后看起來像這樣:

SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc

最后這個在服務端生成並且要發送給客戶端的 Token 看起來像這樣:

eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc

客戶端收到這個 Token 以后把它存儲下來,下回向服務端發送請求的時候就帶着這個 Token 。服務端收到這個 Token ,然后進行驗證,通過以后就會返回給客戶端想要的資源。

簽發與驗證 JWT

在應用里實施使用基於 JWT 這種 Token 的身份驗證方法,你可以先去找一個簽發與驗證 JWT 的功能包。無論你的后端應用使用的是什么樣的程序語言,系統,或者框架,你應該都可以找到提供類似功能的包。

下面我們在一個 Node.js 項目里,用最簡單的方式來演示一下簽發還有驗證 JWT 的方法。練習有個視頻版本,你可以參考《 JWT:JSON Web Token 》這個免費的視頻課程。

項目代碼:https://github.com/ninghao/jwt-demo

准備項目

准備一個簡單的 Node.js 項目:

cd ~/desktop
mkdir jwt-demo
cd jwt-demo
npm init -y

 

安裝簽發與驗證 JWT 的功能包,我用的叫 jsonwebtoken,在項目里安裝一下這個包:

npm install jsonwebtoken --save

簽發 JWT

在項目里隨便添加一個 .js 文件,比如 index.js,在文件里添加下面這些代碼:

const jwt = require('jsonwebtoken')

// Token 數據
const payload = {
  name: 'wanghao',
  admin: true
}

// 密鑰
const secret = 'ILOVENINGHAO'

// 簽發 Token
const token = jwt.sign(payload, secret, { expiresIn: '1day' })

// 輸出簽發的 Token
console.log(token)

 

非常簡單,就是用了剛剛為項目安裝的 jsonwebtoken 里面提供的 jwt.sign 功能,去簽發一個 token。這個 sign 方法需要三個參數:

  1. playload:簽發的 token 里面要包含的一些數據。
  2. secret:簽發 token 用的密鑰,在驗證 token 的時候同樣需要用到這個密鑰。
  3. options:一些其它的選項。

在命令行下面,用 node 命令,執行一下項目里的 index.js 這個文件(node index.js),會輸出應用簽發的 token

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlLCJpYXQiOjE1MjkwMzM5MDYsImV4cCI6MTUyOTEyMDMwNn0.DctA2QlUCrM6wLWkIO78wBVN0NLpjoIq4T5B_2WJ-PU

上面的 Token 內容並沒有加密,所以如果用一些 JWT 解碼功能,可以看到 Token 里面包含的內容,內容由三個部分組成,像這樣:

// header
{
  "alg": "HS256", 
  "typ": "JWT"
}

// payload
{
  "admin": true, 
  "iat": 1529033906, 
  "name": "wanghao", 
  "exp": 1529120306
}

// signature
DctA2QlUCrM6wLWkIO78wBVN0NLpjoIq4T5B_2WJ-PU

 

假設用戶通過了某種身份驗證,你就可以使用上面的簽發 Token 的功能為用戶簽發一個 Token。一般在客戶端那里會把它保存在 Cookie 或 LocalStorage 里面。

用戶下次向我們的應用請求受保護的資源的時候,可以在請求里帶着我們給它簽發的這個 Token,后端應用收到請求,檢查簽名,如果驗證通過確定這個 Token 是我們自己簽發的,那就可以為用戶響應回他需要的資源。

驗證 JWT

驗證 JWT 的用效性,確定一下用戶的 JWT 是我們自己簽發的,首先要得到用戶的這個 JWT Token,然后用 jwt.verify這個方法去做一下驗證。這個方法是 Node.js 的 jsonwebtoken 這個包里提供的,在其它的應用框架或者系統里,你可能會找到類似的方法來驗證 JWT。

打開項目的 index.js 文件,里面添加幾行代碼:

// 驗證 Token
jwt.verify(token, 'bad secret', (error, decoded) => {
  if (error) {
    console.log(error.message)
    return
  }
  console.log(decoded)
})

 

把要驗證的 Token 數據,還有簽發這個 Token 的時候用的那個密鑰告訴 verify 這個方法,在一個回調里面有兩個參數,error 表示錯誤,decoded 是解碼之后的 Token 數據。

執行:

node ~/desktop/jwt-demo/index.js

 

輸出:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlLCJpYXQiOjE1MjkwMzQ3MzMsImV4cCI6MTUyOTEyMTEzM30.swXojmu7VimFu3BoIgAxxpmm2J05dvD0HT3yu10vuqU

invalid signature

 

注意輸出了一個 invalid signature ,表示 Token 里的簽名不對,這是因為我們組長 verify 方法提供的密鑰並不是簽發 Token 的時候用的那個密鑰。這樣修改一下:

jwt.verify(token, secret, (error, decoded) => { ...

 

再次運行,會輸出類似的數據:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlLCJpYXQiOjE1MjkwMzUzODYsImV4cCI6MTUyOTEyMTc4Nn0.mkNrt4TfcfmP22xd3C_GQn8qnUmlB39dKT9SpIBTBGI

{ name: 'wanghao', admin: true, iat: 1529035386, exp: 1529121786 }

 

RS256 算法

默認簽發還有驗證 Token 的時候用的是 HS256 算法,這種算法需要一個密鑰(密碼)。我們還可以使用 RS256 算法簽發與驗證 JWT。這種方法可以讓我們分離開簽發與驗證,簽發時需要用一個密鑰,驗證時使用公鑰,也就是有公鑰的地方只能做驗證,但不能簽發 JWT。

在項目下面創建一個新的目錄,里面可以存儲即將生成的密鑰與公鑰文件。

cd ~/desktop/jwt-demo
mkdir config
cd config

 

密鑰

先生成一個密鑰文件:

ssh-keygen -t rsa -b 2048 -f private.key

 

公鑰

基於上面生成的密鑰,再去創建一個對應的公鑰:

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:

// 簽發 Token
const tokenRS256 = jwt.sign(payload, privateKey, { algorithm: 'RS256' })

// 輸出簽發的 Token
console.log('RS256 算法:', tokenRS256)

 

驗證 JWT(RS256 算法)

驗證使用 RS256 算法簽發的 JWT,需要在文件系統上讀取公鑰文件里的內容。然后用 jwt 的 verify 方法去做驗證。

// 獲取驗證 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)
})

 

相關課程

  1. JWT:JSON Web Token
  2. Node.js:基於 Token 的身份驗證
  3. WordPress 開發:身份驗證(JWT)》
  4. 微信小程序:應用后台__身份驗證 #3


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM