悠悠楠杉
深入解析JWT:JavaScript实现与Token验证实战
在现代Web开发中,JSON Web Token(JWT)已成为前后端分离架构下身份认证的主流方案。本文将手把手带你实现完整的JWT工作流,并揭示那些容易被忽视的安全细节。
一、JWT的核心组成
JWT由三部分组成,通过点号连接:
javascript
// 典型结构示例
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
- Header:指定算法和类型
- Payload:存放实际数据的 claims 对象
- Signature:前两部分的加密签名
二、前端Token生成实现
浏览器端推荐使用jsrsasign库处理签名:
javascript
// 安装:npm install jsrsasign
import { KJUR, hextob64 } from 'jsrsasign';
function generateJWT(payload, secret) {
const header = { alg: 'HS256', typ: 'JWT' };
const sHeader = JSON.stringify(header);
const sPayload = JSON.stringify(payload);
const sJWT = KJUR.jws.JWS.sign(
'HS256',
sHeader,
sPayload,
{ utf8: secret }
);
return sJWT;
}
// 使用示例
const userData = {
userId: 'u_001',
role: 'admin',
exp: Math.floor(Date.now()/1000) + 3600 // 1小时后过期
};
const token = generateJWT(userData, 'your-256-bit-secret');
三、服务端验证关键步骤
Node.js环境验证逻辑(使用jsonwebtoken库):
javascript
const jwt = require('jsonwebtoken');
function verifyToken(token, secret) {
try {
// 同步验证方法(生产环境建议用异步)
const decoded = jwt.verify(token, secret, {
algorithms: ['HS256'],
clockTolerance: 30 // 允许30秒时钟偏移
});
// 检查自定义声明
if (decoded.role !== 'admin') {
throw new Error('Insufficient privileges');
}
return { isValid: true, payload: decoded };
} catch (err) {
// 细分错误类型
const errorMap = {
'TokenExpiredError': '令牌已过期',
'JsonWebTokenError': '无效令牌',
'NotBeforeError': '令牌尚未生效'
};
return {
isValid: false,
error: errorMap[err.name] || '验证失败'
};
}
}
四、前端存储最佳实践
安全存储Token的三种方案对比:
HttpOnly Cookie(最安全)
javascript // 服务端设置 res.cookie('token', generatedToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 3600000 });
localStorage + 内存缓存javascript
// 页面加载时
let inMemoryToken = null;
function setToken(token) {
localStorage.setItem('jwt', token);
inMemoryToken = token; // 减少读取localStorage频率
}
// 注意监听storage事件处理多标签页同步
window.addEventListener('storage', (event) => {
if (event.key === 'jwt') {
inMemoryToken = event.newValue;
}
});
Service Worker管理(PWA场景)javascript
// sw.js中拦截请求添加Authorization头
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
const fetchRequest = event.request.clone();
return fetch(fetchRequest).then((fetchResponse) => {
if (!fetchRequest.url.includes('/api/')) {
return fetchResponse;
}const newHeaders = new Headers(fetchResponse.headers); newHeaders.append('Authorization', `Bearer ${getToken()}`); return new Response(fetchResponse.body, { status: fetchResponse.status, headers: newHeaders });
});
})
);
});
五、防御CSRF的进阶方案
即使使用JWT,仍需防范CSRF攻击:
SameSite Cookie属性
javascript // Express设置 app.use((req, res, next) => { res.cookie('csrf_token', randomBytes(32).toString('hex'), { sameSite: 'strict', secure: true }); next(); });
双重提交验证
javascript // 前端在请求头中添加 fetch('/api/protected', { headers: { 'X-CSRF-TOKEN': getCookie('csrf_token'), 'Authorization': `Bearer ${getJWT()}` } });
加密的JWT payload
javascript // 对敏感字段加密 const crypto = require('crypto'); const cipher = crypto.createCipheriv('aes-256-cbc', key, iv); let encrypted = cipher.update(JSON.stringify(payload), 'utf8', 'hex'); encrypted += cipher.final('hex');
六、性能优化技巧
- 短时效Token + 刷新机制javascript
// 生成15分钟有效期的access_token
const accessToken = jwt.sign(
{ userId: 123, refreshId: 'xyz' },
secret,
{ expiresIn: '15m' }
);
// 配套7天有效期的refresh_token
const refreshToken = jwt.sign(
{ userId: 123, tokenId: 'xyz' },
refreshSecret,
{ expiresIn: '7d' }
);
- 黑名单快速失效
javascript // Redis存储失效Token async function invalidateToken(token) { const decoded = jwt.decode(token); await redis.set( `jwt:invalid:${decoded.jti}`, '1', 'EX', decoded.exp - Math.floor(Date.now()/1000) ); }
通过以上实现方案,我们构建了包含安全防御、性能优化的完整JWT工作流。需要注意的是,任何认证方案都需要根据业务场景调整,建议在关键操作处增加二次认证(如短信验证码)提升安全性。