淺析nodejs中怎麼使用JWT?

2023-01-04 22:00:53
本篇文章帶大家瞭解一下JWT,介紹一下JWT在node中的應用,以及JWT的優缺點,希望對大家有所幫助!

什麼是JWT

JWT也就是JSON Web Token的縮寫,也就是為了在網路應用環境中一種認證解決方案,在傳統的認證機制中,無非是一下幾個步驟:

1. 使用者將賬號密碼傳送到伺服器;

2. 伺服器通過驗證賬號密碼後,會在當前session中儲存一些使用者相關的資訊,使用者角色或者過期時間等等;

3. 伺服器給使用者一個session_id, 寫入使用者的Cookie或者使用者端自行儲存在本地;

4. 使用者每次請求服務,都需要帶上這個session_id,或許會通過Cookie,或者其他的方式;

5. 伺服器接收到後,回去資料庫查詢當前的session_id,校驗該使用者是否有許可權;
登入後複製

這種模式有一種優勢在於,伺服器隨時可以終止使用者的許可權,可以去資料庫修改或者刪除當前使用者的session資訊。但是也有一點不好的,就是如果是伺服器叢集的話,所有的機器就需要共用這些session資訊,確保每臺伺服器都能夠獲取到相同的session儲存資訊。雖然可以解決這些問題,但是工程量巨大。

JWT方案的優勢呢,就是不儲存這些資訊,token資料儲存在使用者端,每次接受請求時,只需要校驗就好。【相關教學推薦:、】

JWT的原理

簡單說一下JWT的原理,其實就是使用者端傳送請求認證的時候,伺服器在認證使用者之後,會生成一個JSON物件,大概包括「你是誰,你是幹嘛的等等,到期時間」這些資訊,重要的是一定要有到期時間;大致格式為:

{
    username: "賊煩字串er",
    role: "世程式碼農",
    endTime: "2022年5月20日"
}
登入後複製

但是不會用這麼膚淺的方式傳給你,它會通過制定的簽名演演算法和你提交的payload的一些資訊進行可逆的簽名演演算法進行簽名後傳輸,大致的格式我用一張圖片表示:

由圖片可以看出,返回的資訊大致分為三部分,左側為簽名之後的結果,也就是返回給使用者端的結果,右側也是就Decoded的原始碼了,三部分由「點」隔開,分別由紅、紫、青三種顏色一一對應:

  • 第一個紅色部分是Header,Header中主要是指定了的方式,圖中的簽名演演算法(預設HS256)就是帶有 SHA-256 的 HMAC 是一種對稱演演算法, 雙方之間僅共用一個金鑰,typ欄位標識為JWT型別;

  • 第二個紫色部分payload,就是一個JSON物件,也就是實際要傳輸的資料,官方有七個欄位可以使用:

    • iss (issuer):簽發人
    • exp (expiration time):過期時間
    • sub (subject):主題
    • aud (audience):受眾
    • nbf (Not Before):生效時間
    • iat (Issued At):簽發時間
    • jti (JWT ID):編號

除了這些欄位,你還可以搞一些自定義的欄位,由於JWT預設是不加密的,所以在使用的時候儘量注意不要使用一些敏感資料。

  • 第三部分就是Signature簽名,這一部分,是由你自己指定且只有伺服器存在的祕鑰,然後使用頭部指定的演演算法通過下面的簽名方法進行簽名。

JWT的簡單使用

下面我們來感受一下具體的使用:

第一步:我們需要搭建一個的專案;通過npm init -y初始化一個專案;之後我們需要安裝依賴,分別按狀expressjsonwebtokennodemon三個依賴:

$ npm i express jsonwebtoken nodemon
登入後複製

之後在package.json中的scripts欄位中新增nodemon app.js命令:

"scripts": {
    "start": "nodemon app.js"
},
登入後複製

第二步:初始化一下node應用,在根目錄下建立app.js檔案;

// app.js

const express = require("express");
const app = express();

app.use(express.json());

app.listen(3000, () => {
  console.log(3000 + " listening..."); // 監聽3000埠
});
登入後複製

第三步:引入jsonwebtoken依賴,並且建立介面和伺服器的私鑰;

// app.js

//...
const jwt = require("jsonwebtoken");

const jwtKey = "~!@#$%^&*()+,";
// ...
登入後複製

這裡面的jwtKey是我們自定義儲存僅限儲存在伺服器中的私鑰,之後我們開始寫一個 /login 介面,用來登入,並且建立原生的模擬資料庫用來校驗,並通過jwt.sign方法進行校驗簽名:

// app.js
const database = {
  username: "username",
  password: "password",
};

app.post("/login", (req, res) => {
  const { username, password } = req.body;
  if (username === database.username && password === database.password) {
    jwt.sign(
      {
        username,
      },
      jwtKey,
      {
        expiresIn: "30S",
      },
      (_, token) => {
        res.json({
          username,
          message: "登陸成功",
          token,
        });
      }
    );
  }
});
登入後複製

上面程式碼中我們建立了database變數來模擬建立了原生的賬號密碼資料庫,用來校驗登陸;接下來建立了一個/loginpost介面,在校驗賬號密碼完全匹配之後,我們通過jsonwebtoken包匯入的jwt物件下的人sign方法進行簽名,這個方法有三種介面簽名:

export function sign(
    payload: string | Buffer | object,
    secretOrPrivateKey: Secret,
    options?: SignOptions,
): string;

export function sign(
    payload: string | Buffer | object,
    secretOrPrivateKey: Secret,
    callback: SignCallback,
): void;

export function sign(
    payload: string | Buffer | object,
    secretOrPrivateKey: Secret,
    options: SignOptions,
    callback: SignCallback,
): void;
登入後複製

這裡用到了函數過載的方式實現介面,我們這裡將實現最後一個介面簽名,第一個引數可以是一個自定義的物件型別,也可以是一個Buffer型別,還可以直接是一個string型別,我們的原始碼使用了object型別,自定義了一些欄位,因為jwt在進行簽名是也會對這些資料一併進行簽名,但是值得注意的是,這裡儘量不要使用敏感資料,因為JWT預設是不加密的,它的核心就是簽名,保證資料未被篡改,而檢查簽名的過程就叫做驗證

當然你也可以對原始Token進行加密後傳輸;

第二個引數:是我們儲存在伺服器用來簽名的祕鑰,通常在使用者端-伺服器端模式中,JWS 使用 JWA 提供的 HS256 演演算法加上一個金鑰即可,這種方式嚴格依賴金鑰,但在分散式場景,可能多個服務都需要驗證JWT,若要在每個服務裡面都儲存金鑰,那麼安全性將會大打折扣,要知道,金鑰一旦洩露,任何人都可以隨意偽造JWT。

第三個引數:是簽名的選項SignOptions,介面的簽名:

export interface SignOptions {
    algorithm?: Algorithm | undefined;
    keyid?: string | undefined;
    expiresIn?: string | number | undefined;
    /** expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms.js).  Eg: 60, "2 days", "10h", "7d" */
    notBefore?: string | number | undefined;
    audience?: string | string[] | undefined;
    subject?: string | undefined;
    issuer?: string | undefined;
    jwtid?: string | undefined;
    mutatePayload?: boolean | undefined;
    noTimestamp?: boolean | undefined;
    header?: JwtHeader | undefined;
    encoding?: string | undefined;
}
登入後複製

這裡我們用的是expiresIn欄位,指定了時效時間,使用方法參考這個檔案;

第四個引數是一個回撥,回撥的第二個引數就是我們通過簽名生成的token,最後將這個token返回給前端,以便儲存到前端本地每次請求是帶上到伺服器端進行驗證。

接下來,我們來驗證一下這個介面: 我是在vscode安裝的REST Client外掛,之後在根目錄建立一個request.http的檔案,檔案內寫上請求的資訊:

POST http://localhost:3000/login
content-type: application/json

{
  "username": "username",
  "password": "password"
}
登入後複製

之後在命令列執行npm run start命令啟動服務,之後在requset.http檔案上方點選Send Request按鈕,傳送請求:

請求成功後,會看到這樣的響應報文:

token欄位就是我們JWT生成的token;

下面來驗證一下這個token是否有效,我們在寫一個登入過後的介面:

app.get("/afterlogin", (req, res) => {
  const { headers } = req;
  
  const token = headers["authorization"].split(" ")[1];
  // 將token放在header的authorization欄位中
  jwt.verify(token, jwtKey, (err, payload) => {
    if (err) return res.sendStatus(403);
    res.json({ message: "認證成功", payload });
  });
});
登入後複製

這段程式碼中,通過獲取請求頭中的authorization欄位中的token進行獲取之前通過JWT生成的token。 之後通過呼叫jwt.verify校驗方法校驗這個token是否有效,這個方法分別有三個引數:

// 有四個介面簽名,可以自行查檔案

export function verify(
    token: string,
    // 需要檢驗的token
    secretOrPublicKey: Secret | GetPublicKeyOrSecret,
    // 定義在伺服器的簽名祕鑰
    callback?: VerifyCallback<JwtPayload | string>,
    // 獲取校驗資訊結果的回撥
): void;
登入後複製

接下來我們把剛才響應的token複製到請求頭中:

###
GET http://localhost:3000/afterlogin
authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXJuYW1lIiwiaWF0IjoxNjUyNzg5NzA3LCJleHAiOjE2NTI3ODk3Mzd9.s9fk3YLhxTUcpUgCfIK4xQN58Hk_XEP5y9GM9A8jBbY
登入後複製

前面的Bearer認證, 是http協定中的標準認證方式

同樣點選Send Request當看到下面圖片的響應,就意味著響應成功:

其實以上就是JWT的一些簡單的用法,接下來再說一下JWT本身存在的優缺點.

JWT的不足

  • JWT佔用的儲存空間其實並不小,如果我們需要簽名做過多的資訊,那麼token很可能會超出cookie的長度限制,例如對比一下這兩張圖片:

很明顯,隨著payload的資訊量增大,token的長度也會增加;

  • 安全性,其實如果token的佔用空間過大,Cookie最大儲存空間只有4kb前端可以儲存在localStorage之類的本地儲存,但是會帶來一個問題,如果不是放在cookie的話,安全性就會大打折扣,就會有通過js指令碼獲取到的風險,就意味著任何hacker都可以拿著它做任何事情;

  • 不靈活的時效性,其實JWT的某方面意義在於使用者token不需要持久化儲存,而是採用伺服器校驗的方式對token進行有效校驗,剛才看到了,簽名也是把到期時間一併簽名的,如果改變到期時間token就會被篡改,由於沒有儲存和手動更改時效的方法,所以很難立刻將這個token刪掉,如果使用者重複登陸兩次,生成兩個token,那麼原則上兩個token都是有效的;

總結

以上主要講了幾點:

  • JWT的原理,主要是通過伺服器的私鑰對JSON的簽名產生的token進行對談;

  • 也介紹了JWT內部資料的組成,是由Header用來指定簽名演演算法和型別的,payload來傳輸JSON資料,Signature來對資料進行簽名演演算法,放置篡改;

  • 具體介紹了一下如何通過nodejs使用JWT,通過sign方法進行資料簽名,verify方法進行簽名驗證;

  • 還介紹了一些JWT的不足:

    • 一個是儲存空間隨著簽名資料量的增大而增加;

    • 再有就是安全性,如果由於儲存空間過大將無法儲存在安全級別相對較高的Cookie中,導致指令碼可以隨意獲取;

    • 再有就是時效性,無法靈活的控制token的時效性;

這個是上面nodejs的demo原始碼,可供參考;

https://github.com/wangzi6224/jwt-usage-nodejs

更多node相關知識,請存取:!

以上就是淺析nodejs中怎麼使用JWT?的詳細內容,更多請關注TW511.COM其它相關文章!