Node.js 使用 express-jwt 解析 JWT

  • 2019 年 11 月 7 日
  • 筆記

Node.js 上 Token 鑒權常用的是 passport,它可以自定義校驗策略,但如果你是用 express 框架,又只是解析 JWT 這種簡單需求,可以嘗試下 express-jwt 這個中間件。

關於 JWT

JWT 全稱 JSON Web Token,是代替傳統 session 認證的解決方案。其原理是服務端生成一個包含用戶唯一標識的 JSON 對象,頒發給客戶端。客戶端請求需要權限的接口時,只要把這個 JSON 再原樣發回給服務端,服務器通過解析就可識別用戶。

它通常是這個樣子:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

這個 JSON 對象通過 . 分成三段,包含了請求頭(加密算法)、負載信息(如 userId、過期時間),還有通過服務端密鑰生成的簽名來保證不被篡改。

這種機制使服務端不再需要存儲 Token,因此是非常輕量的用戶認證方案。並且對於微服務這種需要不同服務間共用 Token 的跨域認證,JWT 是目前的首選。

關於 express-jwt

express-jwt 是 Node.js 的一個開源庫,由 ID 認證服務提供商 auth0 開發,是專用於 express 框架下解析 JWT 的中間件。

它使用非常簡單,而且會自動把 JWT 的 payload 部分賦值於 req.user,方便邏輯部分調用。

開始使用

安裝

npm install express-jwt

加入中間件

const expressJWT = require('express-jwt')    app.use(expressJWT({    secret: 'secret12345'  // 簽名的密鑰 或 PublicKey  }).unless({    path: ['/login', '/signup']  // 指定路徑不經過 Token 解析  }))

生成 Token

生成 Token 的方式依然使用 jsonwebtoken,比如將下列代碼加入到登錄接口的返回部分:

const jwt = require('jsonwebtoken')    app.post('/login', function (req, res) {    // 注意默認情況 Token 必須以 Bearer+空格 開頭    const token = 'Bearer ' + jwt.sign(      {        _id: user._id,        admin: user.role === 'admin'      },      'secret12345',      {        expiresIn: 3600 * 24 * 3      }    )    res.json({      status: 'ok',      data: { token: token }    })  })

獲取解析內容

當收到帶 Token 的請求,如果解析成功,就可以在路由回調里通過 req.user 來訪問:

app.get('/protected', function (req, res) {    if (!req.user.admin)      return res.sendStatus(401)    res.sendStatus(200)  })

req.user 實際就是 JWT 的 payload 部分:

{    _id: '5dbbc7daaf7dfe003680ba39',    admin: true,    iat: 1572587484,    exp: 1573192284  }

解析失敗

如果解析失敗,會拋出 UnauthorizedError,可以通過後置中間件來捕獲:

app.use(function (err, req, res, next) {    if (err.name === 'UnauthorizedError') {      res.status(401).send('invalid token')    }  })

修改結果字段

默認解析結果會賦值在 req.user,也可以通過 requestProperty 來修改:

app.use(jwt({    secret: 'secret12345',    requestProperty: 'auth'  }))

允許無 Token 請求

當接口允許不帶 Token 和帶 Token 兩種狀態的訪問時(比如文章詳情登錄後判斷點贊),可以通過 credentialsRequired: false 來對無 Token 請求不進行解析和拋出異常。

app.use(jwt({    secret: 'secret12345',    credentialsRequired: false  }))

自定義解析

通過 getToken 也可以自定義一些解析邏輯,比如使用其他 Header 字段,自定義拋出異常等:

app.use(jwt({    secret: 'secret12345',    credentialsRequired: false,    getToken: function fromHeaderOrQuerystring (req) {      if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {        return req.headers.authorization.split(' ')[1]      } else if (req.query && req.query.token) {        return req.query.token      }      return null    }  }))

吊銷 Token

在 JWT 機制中,由於 Token 通常不進行存儲,如果想吊銷某一條 Token,一般都是通過被動的方式。

常用的方式是建立某個字段的黑名單(比如 TokenId),對所有 Token 進行過濾,express-jwt 專門提供了回調來處理這種情況:

const jwt = require('express-jwt')  const blacklist = require('./blacklist')    let isRevokedCallback = function(req, payload, done){    let issuer = payload.iss    let tokenId = payload.jti      blacklist.getRevokedToken(issuer, tokenId, function(err, token){      if (err) { return done(err) }      return done(null, !!token)  // 第二個參數為 true 則不通過    })  }    app.use(jwt({    secret: 'secret12345',    isRevoked: isRevokedCallback  }))

更多用法可以查看 官方文檔


本文屬於原創,首發於微信公眾號「面向人生編程」,如需轉載請後台留言。