Node.js實現(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
它由三部分組成,用點(.)分隔:
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 中實現(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過期] → [要求重新登錄]
實現(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: '歡迎管理員' });
});
五、安全最佳實踐
密鑰管理:
- 永遠(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: 如何實現(xiàn)強(qiáng)制下線?
A: JWT 本身難以實現(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ù)器運行在 http://localhost:${PORT}`);
});
結(jié)語
JWT 就像數(shù)字世界的護(hù)照,它輕巧、自包含且安全。通過本文的學(xué)習(xí),你應(yīng)該已經(jīng)掌握了:
- JWT 的結(jié)構(gòu)和工作原理
- 如何在 Node.js 中生成和驗證 JWT
- 各種相關(guān)方法和配置選項
- 高級應(yīng)用場景和安全實踐
記住,沒有絕對安全的系統(tǒng),JWT 只是工具,合理的使用方式和適當(dāng)?shù)陌踩胧┎攀顷P(guān)鍵?,F(xiàn)在就去你的 Node.js 項目中實踐這些知識吧!
以上就是Node.js實現(xiàn)JWT認(rèn)證的流程步驟的詳細(xì)內(nèi)容,更多關(guān)于Node.js JWT認(rèn)證的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
node異步使用await和不用await的區(qū)別實例分析
這篇文章主要介紹了node異步使用await和不用await的區(qū)別,結(jié)合實例形式分析了node.js異步使用await和不用await的實例中,同步與異步執(zhí)行的區(qū)別,需要的朋友可以參考下2023-06-06
express框架+bootstrap美化ejs模板實例分析
這篇文章主要介紹了express框架+bootstrap美化ejs模板,結(jié)合實例形式分析了express框架引入bootstrap結(jié)合ejs模版引擎相關(guān)操作技巧與注意事項,需要的朋友可以參考下2023-05-05
詳解使用Node.js 將txt文件轉(zhuǎn)為Excel文件
這篇文章主要介紹了詳解使用Node.js 將txt文件轉(zhuǎn)為Excel文件,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07
Node.js?連接?MySql?統(tǒng)計組件屬性的使用情況解析
這篇文章主要為大家介紹了Node.js?連接?MySql?統(tǒng)計組件屬性的使用情況解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
node.js使用express-jwt報錯:expressJWT?is?not?a?function解決
這篇文章主要給大家介紹了關(guān)于node.js使用express-jwt報錯:expressJWT?is?not?a?function解決的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2023-03-03

