Node.js實(shí)現(xiàn)JWT認(rèn)證的流程步驟
一、JWT 是什么?為什么需要它?
想象一下你去參加一個音樂會,入場時需要出示門票。這張門票包含你的座位信息,并有防偽標(biāo)識。JWT 就像這張數(shù)字門票:
- 包含信息:存儲用戶身份數(shù)據(jù)(如用戶ID)
- 防偽標(biāo)識:通過簽名防止篡改
- 有效期:像門票一樣有使用期限
傳統(tǒng) session 與 JWT 對比
特性 | Session | JWT |
---|---|---|
存儲位置 | 服務(wù)器內(nèi)存/數(shù)據(jù)庫 | 客戶端 |
擴(kuò)展性 | 跨服務(wù)器共享困難 | 天然支持分布式 |
跨域支持 | 需要額外配置 | 原生支持 |
移動端友好度 | 一般 | 非常好 |
安全性 | 依賴 Cookie 安全 | 依賴 Token 存儲方式 |
二、JWT 的結(jié)構(gòu)解析
一個 JWT 看起來像這樣:xxxxx.yyyyy.zzzzz
它由三部分組成,用點(diǎn)(.)分隔:
Header (頭部) - xxxxx
{ "alg": "HS256", // 簽名算法 "typ": "JWT" // 令牌類型 }
Payload (負(fù)載) - yyyyy
{ "sub": "1234567890", // 主題(用戶ID) "name": "John Doe", // 自定義數(shù)據(jù) "iat": 1516239022 // 簽發(fā)時間 }
Signature (簽名) - zzzzz
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
可視化流程:
[Header JSON] → Base64編碼 → xxxxx [Payload JSON] → Base64編碼 → yyyyy [xxxxx.yyyyy + 密鑰] → 簽名算法 → zzzzz 最終令牌:xxxxx.yyyyy.zzzzz
三、Node.js 中實(shí)現(xiàn) JWT
1. 安裝 jsonwebtoken 包
const jwt = require('jsonwebtoken'); const secret = 'your-secret-key'; // 應(yīng)該存儲在環(huán)境變量中 // 用戶登錄成功后生成token function generateToken(user) { return jwt.sign( { userId: user.id, username: user.username, role: user.role }, secret, { expiresIn: '1h', // 1小時后過期 issuer: 'your-company', // 簽發(fā)者 audience: 'your-app-name' // 接收方 } ); }
參數(shù)詳解表:
參數(shù) | 類型 | 必填 | 說明 |
---|---|---|---|
payload | Object/String | 是 | 要編碼的數(shù)據(jù) |
secret | String | 是 | 簽名密鑰 |
options | Object | 否 | 配置選項 |
常用 options:
選項 | 示例值 | 說明 |
---|---|---|
expiresIn | ‘1h’/‘15m’/‘7d’ | 有效期 |
algorithm | ‘HS256’ | 簽名算法 |
issuer | ‘your-app’ | 簽發(fā)者 |
audience | ‘client-app’ | 接收方 |
subject | ‘user-auth’ | 主題 |
3. 驗證 JWT
function verifyToken(token) { try { return jwt.verify(token, secret, { issuer: 'your-company', audience: 'your-app-name' }); } catch (err) { console.error('Token驗證失敗:', err.message); return null; } } // 使用示例 const token = generateToken({id: 1, username: 'john', role: 'admin'}); const decoded = verifyToken(token);
驗證流程示意圖:
客戶端請求 → [攜帶Token] → 服務(wù)器 ↓ [提取Authorization頭] ↓ [拆分Bearer和Token] ↓ [jwt.verify()驗證] ↓ [有效] → 繼續(xù)處理請求 ↓ [無效] → 返回401錯誤
4. 錯誤處理大全
JWT 驗證可能拋出以下錯誤:
錯誤類型 | 觸發(fā)條件 | 處理建議 |
---|---|---|
JsonWebTokenError | 無效token | 返回401 |
TokenExpiredError | token過期 | 返回401,提示刷新 |
NotBeforeError | 未到生效時間 | 等待或返回403 |
SyntaxError | token格式錯誤 | 返回400 |
錯誤處理增強(qiáng)版:
function handleTokenError(err) { switch(err.name) { case 'JsonWebTokenError': return { status: 401, message: '無效令牌' }; case 'TokenExpiredError': return { status: 401, message: '令牌已過期,請重新登錄' }; case 'NotBeforeError': return { status: 403, message: '令牌尚未生效' }; default: return { status: 400, message: '令牌處理錯誤' }; } }
四、高級應(yīng)用場景
1. 刷新令牌機(jī)制
[登錄成功] ↓ [發(fā)放 access_token (短有效期) + refresh_token (長有效期)] ↓ [access_token過期] → [用refresh_token獲取新access_token] ↓ [refresh_token過期] → [要求重新登錄]
實(shí)現(xiàn)代碼:
// 生成令牌對 function generateTokenPair(user) { const accessToken = jwt.sign( { userId: user.id }, secret, { expiresIn: '15m' } ); const refreshToken = jwt.sign( { userId: user.id, tokenType: 'refresh' }, secret, { expiresIn: '7d' } ); return { accessToken, refreshToken }; } // 刷新access token function refreshAccessToken(refreshToken) { const decoded = jwt.verify(refreshToken, secret); if (decoded.tokenType !== 'refresh') { throw new Error('非法的refresh token'); } return jwt.sign({ userId: decoded.userId }, secret, { expiresIn: '15m' }); }
2. 在不同路由中的驗證中間件
// 基礎(chǔ)驗證中間件 function authenticateJWT(req, res, next) { const authHeader = req.headers.authorization; if (authHeader) { const token = authHeader.split(' ')[1]; jwt.verify(token, secret, (err, user) => { if (err) { const error = handleTokenError(err); return res.status(error.status).json(error); } req.user = user; next(); }); } else { res.sendStatus(401); } } // 角色檢查中間件 function requireRole(role) { return (req, res, next) => { if (req.user?.role !== role) { return res.status(403).json({ message: '權(quán)限不足' }); } next(); }; } // 使用示例 router.get('/admin', authenticateJWT, requireRole('admin'), (req, res) => { res.json({ message: '歡迎管理員' }); });
五、安全最佳實(shí)踐
密鑰管理:
- 永遠(yuǎn)不要將密鑰硬編碼在代碼中
- 使用環(huán)境變量或密鑰管理服務(wù)
- 定期輪換密鑰
Token 存儲:
- 前端:使用 HttpOnly + Secure 的 Cookie 比 localStorage 更安全
- 避免在 URL 中傳遞 token
額外安全措施:
// 示例:增加IP綁定 function generateToken(user, ip) { return jwt.sign({ userId: user.id, ip: ip // 綁定用戶當(dāng)前IP }, secret, { expiresIn: '1h' }); } function verifyToken(token, ip) { const decoded = jwt.verify(token, secret); if (decoded.ip !== ip) { throw new Error('IP地址不匹配'); } return decoded; }
六、常見問題解答
Q: JWT 和 Session Cookie 哪個更好?
A: 沒有絕對的好壞,取決于場景:
- 需要分布式/無狀態(tài) → JWT
- 需要即時撤銷 → Session
- 移動端應(yīng)用 → JWT
- 傳統(tǒng)Web應(yīng)用 → 兩者皆可
Q: JWT 過期后如何處理?
A: 兩種方案:
- 讓用戶重新登錄
- 使用refresh token機(jī)制自動獲取新token
Q: 如何實(shí)現(xiàn)強(qiáng)制下線?
A: JWT 本身難以實(shí)現(xiàn),可以考慮:
- 使用短有效期 + refresh token
- 維護(hù)一個黑名單(部分犧牲無狀態(tài)特性)
- 在token中存儲版本號,修改版本號使舊token失效
七、完整示例代碼
const express = require('express'); const jwt = require('jsonwebtoken'); const app = express(); require('dotenv').config(); const SECRET = process.env.JWT_SECRET || 'fallback-secret'; const PORT = process.env.PORT || 3000; // 模擬用戶數(shù)據(jù)庫 const users = [ { id: 1, username: 'admin', password: 'admin123', role: 'admin' }, { id: 2, username: 'user', password: 'user123', role: 'user' } ]; app.use(express.json()); // 登錄路由 app.post('/login', (req, res) => { const { username, password } = req.body; const user = users.find(u => u.username === username && u.password === password); if (!user) { return res.status(401).json({ message: '用戶名或密碼錯誤' }); } const token = jwt.sign( { userId: user.id, role: user.role }, SECRET, { expiresIn: '15m' } ); res.json({ token }); }); // 受保護(hù)路由 app.get('/profile', authenticateJWT, (req, res) => { const user = users.find(u => u.id === req.user.userId); res.json({ id: user.id, username: user.username, role: user.role }); }); // 管理員路由 app.get('/admin-stats', authenticateJWT, (req, res, next) => { if (req.user.role !== 'admin') { return res.status(403).json({ message: '需要管理員權(quán)限' }); } res.json({ stats: '敏感管理數(shù)據(jù)' }); }); // JWT驗證中間件 function authenticateJWT(req, res, next) { const authHeader = req.headers.authorization; if (authHeader && authHeader.startsWith('Bearer ')) { const token = authHeader.split(' ')[1]; jwt.verify(token, SECRET, (err, user) => { if (err) { const error = handleTokenError(err); return res.status(error.status).json(error); } req.user = user; next(); }); } else { res.status(401).json({ message: '需要認(rèn)證令牌' }); } } // 錯誤處理函數(shù) function handleTokenError(err) { switch(err.name) { case 'JsonWebTokenError': return { status: 401, message: '無效令牌' }; case 'TokenExpiredError': return { status: 401, message: '令牌已過期,請重新登錄' }; default: return { status: 400, message: '令牌處理錯誤' }; } } app.listen(PORT, () => { console.log(`服務(wù)器運(yùn)行在 http://localhost:${PORT}`); });
結(jié)語
JWT 就像數(shù)字世界的護(hù)照,它輕巧、自包含且安全。通過本文的學(xué)習(xí),你應(yīng)該已經(jīng)掌握了:
- JWT 的結(jié)構(gòu)和工作原理
- 如何在 Node.js 中生成和驗證 JWT
- 各種相關(guān)方法和配置選項
- 高級應(yīng)用場景和安全實(shí)踐
記住,沒有絕對安全的系統(tǒng),JWT 只是工具,合理的使用方式和適當(dāng)?shù)陌踩胧┎攀顷P(guān)鍵?,F(xiàn)在就去你的 Node.js 項目中實(shí)踐這些知識吧!
以上就是Node.js實(shí)現(xiàn)JWT認(rèn)證的流程步驟的詳細(xì)內(nèi)容,更多關(guān)于Node.js JWT認(rèn)證的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Node.js 中的流Stream模塊簡介及如何使用流進(jìn)行數(shù)據(jù)處理
Node.js中的流(Stream)模塊用于高效處理流式數(shù)據(jù),包括可讀流、可寫流、雙邊流和轉(zhuǎn)換流等,通過`fs.createReadStream`和`.pipe`方法可以方便地讀取文件并寫入控制臺或處理網(wǎng)絡(luò)請求,在實(shí)際開發(fā)中,需要注意錯誤處理、資源管理和性能優(yōu)化等問題2025-03-03nodejs npm install全局安裝和本地安裝的區(qū)別
這篇文章主要介紹了nodejs npm install 全局安裝和非全局安裝的區(qū)別,即帶參數(shù)-g和不帶參數(shù)-g安裝的區(qū)別,需要的朋友可以參考下2014-06-06