基於Token的身份驗證——JSON Web Token

基於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
  • 每個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
  • 效果圖如下:

init

  • 安裝簽發與驗證JWT的功能包,這裏使用的是jsonwebtoken,在項目裏面安裝這個包。

  • 效果圖如下:

jsonwebtoken_install

  • package.json效果圖:

jsonwebtoken_package


簽發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
  • 效果圖如下:

config


簽發JWT(RS256算法)

  • 用RS256算法簽發JWT的時候,需要從文件系統上讀取創建的密鑰文件裏的內容。
const fs = require('fs')

// 獲取簽發 JWT 時需要用的密鑰
const privateKey = fs.readFileSync('./config/private.key')
  • 效果圖:

RS256

  • 簽發仍然使用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)
})
  • 效果圖:

RS256_verify


代碼地址

  • jwt_demo,個人驗證代碼的github地址。

JackDan Thinking

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章