這篇文章主要介紹“Node如何實(shí)現(xiàn)JWT鑒權(quán)機(jī)制”的相關(guān)知識(shí),小編通過實(shí)際案例向大家展示操作過程,操作方法簡單快捷,實(shí)用性強(qiáng),希望這篇“Node如何實(shí)現(xiàn)JWT鑒權(quán)機(jī)制”文章能幫助大家解決問題。
創(chuàng)新互聯(lián)建站從2013年創(chuàng)立,先為嘉興等服務(wù)建站,嘉興等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為嘉興企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。
一種技術(shù)的出現(xiàn),就是彌補(bǔ)另一種技術(shù)的的缺陷。在JWT出現(xiàn)之前,Session 認(rèn)證機(jī)制需要配合 Cookie 才能實(shí)現(xiàn)。由于 Cookie 默認(rèn)不支持跨域訪問,所以,當(dāng)涉及到前端跨域請(qǐng)求后端接口的時(shí)候,需要做很多額外的配置,才能實(shí)現(xiàn)跨域 Session 認(rèn)證。
注意:
當(dāng)前端請(qǐng)求后端接口不存在跨域問題的時(shí)候,推薦使用 Session 身份認(rèn)證機(jī)制。
當(dāng)前端需要跨域請(qǐng)求后端接口的時(shí)候,不推薦使用 Session 身份認(rèn)證機(jī)制,推薦使用 JWT 認(rèn)證機(jī)制。
JWT(英文全稱:JSON Web Token)是目前最流行的跨域認(rèn)證解決方案。本質(zhì)就是一個(gè)字符串書寫規(guī)范,如下圖,作用是用來在用戶和服務(wù)器之間傳遞安全可靠的信息
在目前前后端分離的開發(fā)過程中,使用token
鑒權(quán)機(jī)制用于身份驗(yàn)證是最常見的方案,流程如下:
服務(wù)器當(dāng)驗(yàn)證用戶賬號(hào)和密碼正確的時(shí)候,給用戶頒發(fā)一個(gè)令牌,這個(gè)令牌作為后續(xù)用戶訪問一些接口的憑證
后續(xù)訪問會(huì)根據(jù)這個(gè)令牌判斷用戶是否有權(quán)限進(jìn)行訪問
總結(jié):用戶的信息通過 Token 字符串的形式,保存在客戶端瀏覽器中。服務(wù)器通過還原Token 字符串的形式來認(rèn)證用戶的身份。
Token
,分成了三部分,頭部(Header)、載荷(Payload)、簽名(Signature),并以.
進(jìn)行拼接。其中頭部和載荷都是以JSON
格式存放數(shù)據(jù),只是進(jìn)行了編碼。格式如下:
Header.Payload.Signature
下面是 JWT 字符串的示例:
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNjQ0ODI3NzI2LCJleHAiOjE2NDQ4Mjc3NTZ9.gdZKg9LkPiQZIgNAZ1Mn14GQd9kZZua-_unwHQoRsKE
注意:Bearer 是手動(dòng)添加的頭部信息,必須攜帶此信息才能解析token !
每個(gè)JWT都會(huì)帶有頭部信息,這里主要聲明使用的算法。聲明算法的字段名為alg
,同時(shí)還有一個(gè)typ
的字段,默認(rèn)JWT
即可。以下示例中算法為HS256:
{ "alg": "HS256", "typ": "JWT" }
因?yàn)镴WT是字符串,所以我們還需要對(duì)以上內(nèi)容進(jìn)行Base64編碼,編碼后字符串如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
載荷即消息體,這里會(huì)存放實(shí)際的內(nèi)容,也就是Token
的數(shù)據(jù)聲明,例如用戶的id
和name
,默認(rèn)情況下也會(huì)攜帶令牌的簽發(fā)時(shí)間iat
,通過還可以設(shè)置過期時(shí)間,如下:
{ "sub": "1234567890", "name": "CoderBin", "iat": 1516239022 }
同樣進(jìn)行Base64編碼后,字符串如下:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
簽名是對(duì)頭部和載荷內(nèi)容進(jìn)行簽名,一般情況,設(shè)置一個(gè)secretKey
,對(duì)前兩個(gè)的結(jié)果進(jìn)行HMACSHA25
算法,公式如下:
Signature = HMACSHA256(base64Url(header)+.+base64Url(payload),secretKey)
一旦前面兩部分?jǐn)?shù)據(jù)被篡改,只要服務(wù)器加密用的密鑰沒有泄露,得到的簽名肯定和之前的簽名不一致
客戶端收到服務(wù)器返回的 JWT 之后,通常會(huì)將它儲(chǔ)存在 localStorage
或 sessionStorage
中。
此后,客戶端每次與服務(wù)器通信,都要帶上這個(gè) JWT 的字符串,從而進(jìn)行身份認(rèn)證。推薦的做法是把 JWT 放在 HTTP 請(qǐng)求頭的 Authorization 字段中,格式如下:
Authorization: Bearer
Token
的使用分成了兩部分:
生成token:登錄成功的時(shí)候,頒發(fā)token
驗(yàn)證token:訪問某些資源或者接口時(shí),驗(yàn)證token
接下來就在 node+express
環(huán)境下帶大家實(shí)現(xiàn)jwt鑒權(quán),最后有完整代碼加注釋,可以直接看最后的代碼
運(yùn)行如下命令,安裝如下兩個(gè) JWT 相關(guān)的包:
npm i jsonwebtoken express-jwt
其中:
jsonwebtoken
用于生成 JWT 字符串
express-jwt
用于驗(yàn)證token,將 JWT 字符串解析還原成 JSON 對(duì)象
在app.js
中
// 導(dǎo)入用于生成 JWT 字符串的包 const jwt = require('jsonwebtoken') // 導(dǎo)入用戶將客戶端發(fā)送過來的 JWT 字符串,解析還原成 JSON 對(duì)象的包 const expressJWT = require('express-jwt')
為了保證 JWT 字符串的安全性,防止 JWT 字符串在網(wǎng)絡(luò)傳輸過程中被別人破解,我們需要專門定義一個(gè)用于加密和解密的 secret 密鑰:
當(dāng)生成 JWT 字符串的時(shí)候,需要使用 secret 密鑰對(duì)用戶的信息進(jìn)行加密,最終得到加密好的 JWT 字符串
當(dāng)把 JWT 字符串解析還原成 JSON 對(duì)象的時(shí)候,需要使用 secret 密鑰進(jìn)行解密
// 這個(gè) secretKey 的是可以是任意的字符串 const secretKey = 'CoderBin ^_^'
調(diào)用 jsonwebtoken包提供的 sign()
方法,將用戶的信息加密成 JWT 字符串,響應(yīng)給客戶端:
參數(shù) 1:用戶的信息對(duì)象
參數(shù) 2:解密的秘鑰
參數(shù) 3:配置對(duì)象,可以配置 token 的有效期
注意:千萬不要把密碼加密到 token 字符串中!
// 登錄接口 app.post('/api/login', function (req, res) { // 將 req.body 請(qǐng)求體中的數(shù)據(jù),轉(zhuǎn)存為 userinfo 常量 const userinfo = req.body // 省略登錄失敗情況下的代碼... // 登錄成功 // 在登錄成功之后,調(diào)用 jwt.sign() 方法生成 JWT 字符串。并通過 token 屬性發(fā)送給客戶端 const tokenStr = jwt.sign( { username: userinfo.username }, secretKey, { expiresIn: '30s' } ) // 向客戶端響應(yīng)成功的消息 res.send({ status: 200, message: '登錄成功!', token: tokenStr // 要發(fā)送給客戶端的 token 字符串 }) })
客戶端每次在訪問那些有權(quán)限接口的時(shí)候,都需要主動(dòng)通過請(qǐng)求頭中的 Authorization 字段,將 Token 字符串發(fā)送到服務(wù)器進(jìn)行身份認(rèn)證。
此時(shí),服務(wù)器可以通過 express-jwt這個(gè)中間件,自動(dòng)將客戶端發(fā)送過來的 Token 解析還原成 JSON 對(duì)象:
express.JWT({ secret: secretKey, algorithms: ['HS256'] })
就是用來解析 Token 的中間件
express-jwt 模塊,現(xiàn)在默認(rèn)為 6版本以上,必須加上: algorithms: ['HS256']
注意:只要配置成功了 express-jwt 這個(gè)中間件,就會(huì)自動(dòng)把解析出來的用戶信息,掛載到 req.user 屬性上
// 1. 使用 app.use() 來注冊(cè)中間件 app.use(expressJWT({ secret: secretKey, algorithms: ['HS256'] }).unless({ path: [/^\/api\//] }))
注意:
secret 必須和 sign 時(shí)候保持一致
可以通過 unless 配置接口白名單,也就是哪些 URL 可以不用經(jīng)過校驗(yàn),像登陸/注冊(cè)都可以不用校驗(yàn)
校驗(yàn)的中間件需要放在需要校驗(yàn)的路由前面,無法對(duì)前面的 URL 進(jìn)行校驗(yàn)
當(dāng) express-jwt 這個(gè)中間件配置成功之后,即可在那些有權(quán)限的接口中,使用 req.user
對(duì)象,來訪問從 JWT 字符串中解析出來的用戶信息了,示例代碼如下:
// 這是一個(gè)有權(quán)限的 API 接口,必須在 Header 中攜帶 Authorization 字段,值為 token,才允許訪問 app.get('/admin/getinfo', function (req, res) { // TODO_05:使用 req.user 獲取用戶信息,并使用 data 屬性將用戶信息發(fā)送給客戶端 console.log(req.user); res.send({ status: 200, message: '獲取用戶信息成功!', data: req.user // 要發(fā)送給客戶端的用戶信息 }) })
當(dāng)使用 express-jwt 解析 Token 字符串時(shí),如果客戶端發(fā)送過來的 Token 字符串過期或不合法,會(huì)產(chǎn)生一個(gè)解析失敗的錯(cuò)誤,影響項(xiàng)目的正常運(yùn)行。我們可以通過 Express 的錯(cuò)誤中間件,捕獲這個(gè)錯(cuò)誤并進(jìn)行相關(guān)的處理,示例代碼如下:
app.use((err, req, res, next) => { // 這次錯(cuò)誤是由 token 解析失敗導(dǎo)致的 if (err.name === 'UnauthorizedError') { return res.send({ status: 401, message: '無效的token' }) } res.send({ status: 500, message: '未知的錯(cuò)誤' }) })
app.js
// 導(dǎo)入 express 模塊 const express = require('express') // 創(chuàng)建 express 的服務(wù)器實(shí)例 const app = express() // TODO_01:安裝并導(dǎo)入 JWT 相關(guān)的兩個(gè)包,分別是 jsonwebtoken 和 express-jwt const jwt = require('jsonwebtoken') const expressJWT = require('express-jwt') // 允許跨域資源共享 const cors = require('cors') app.use(cors()) // 解析 post 表單數(shù)據(jù)的中間件 const bodyParser = require('body-parser') // 這里用內(nèi)置的中間件也行: app.use(express.urlencoded({ extended: false })) app.use(bodyParser.urlencoded({ extended: false })) // TODO_02:定義 secret 密鑰,建議將密鑰命名為 secretKey // 這個(gè) secretKey 的是可以是任意的字符串 const secretKey = 'smiling ^_^' // TODO_04:注冊(cè)將 JWT 字符串解析還原成 JSON 對(duì)象的中間件 // 1. 使用 app.use() 來注冊(cè)中間件 // 2. express.JWT({ secret: secretKey, algorithms: ['HS256'] }) 就是用來解析 Token 的中間件 // 2.1 express-jwt 模塊,現(xiàn)在默認(rèn)為 6版本以上,必須加上: algorithms: ['HS256'] // 3. .unless({ path: [/^\/api\//] }) 用來指定哪些接口不需要訪問權(quán)限 // 4. 注意:只要配置成功了 express-jwt 這個(gè)中間件,就會(huì)自動(dòng)把解析出來的用戶信息,掛載到 req.user 屬性上 app.use(expressJWT({ secret: secretKey, algorithms: ['HS256'] }).unless({ path: [/^\/api\//] })) // 登錄接口 app.post('/api/login', function (req, res) { // 將 req.body 請(qǐng)求體中的數(shù)據(jù),轉(zhuǎn)存為 userinfo 常量 const userinfo = req.body // 登錄失敗 if (userinfo.username !== 'admin' || userinfo.password !== '000000') { return res.send({ status: 400, message: '登錄失?。?#39; }) } // 登錄成功 // TODO_03:在登錄成功之后,調(diào)用 jwt.sign() 方法生成 JWT 字符串。并通過 token 屬性發(fā)送給客戶端 // 參數(shù) 1:用戶的信息對(duì)象 // 參數(shù) 2:解密的秘鑰 // 參數(shù) 3:配置對(duì)象,可以配置 token 的有效期 // 記住:千萬不要把密碼加密到 token 字符串中! const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '30s' }) res.send({ status: 200, message: '登錄成功!', token: tokenStr // 要發(fā)送給客戶端的 token 字符串 }) }) // 這是一個(gè)有權(quán)限的 API 接口,必須在 Header 中攜帶 Authorization 字段,值為 token,才允許訪問 app.get('/admin/getinfo', function (req, res) { // TODO_05:使用 req.user 獲取用戶信息,并使用 data 屬性將用戶信息發(fā)送給客戶端 console.log(req.user); res.send({ status: 200, message: '獲取用戶信息成功!', data: req.user // 要發(fā)送給客戶端的用戶信息 }) }) // TODO_06:使用全局錯(cuò)誤處理中間件,捕獲解析 JWT 失敗后產(chǎn)生的錯(cuò)誤 app.use((err, req, res, next) => { // 這次錯(cuò)誤是由 token 解析失敗導(dǎo)致的 if (err.name === 'UnauthorizedError') { return res.send({ status: 401, message: '無效的token' }) } res.send({ status: 500, message: '未知的錯(cuò)誤' }) }) // 調(diào)用 app.listen 方法,指定端口號(hào)并啟動(dòng)web服務(wù)器 app.listen(8888, function () { console.log('Express server running at http://127.0.0.1:8888') })
借助 postman 工具測(cè)試接口
關(guān)于“Node如何實(shí)現(xiàn)JWT鑒權(quán)機(jī)制”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí),可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,小編每天都會(huì)為大家更新不同的知識(shí)點(diǎn)。