Appearance
jsonwebtoken
简介
jsonwebtoken 是一个用于生成和验证 JSON Web Tokens (JWT) 的 Node.js 库。JWT 是一种紧凑的、URL 安全的方式,用于在各方之间安全地传输信息。
主要特性
- 支持多种签名算法(HMAC、RSA、ECDSA)
- 可自定义 Token 有效期
- 支持标准 JWT 声明(iss、sub、aud、exp 等)
- 提供同步和异步 API
- 支持自定义 Header 和 Payload
- 完整的错误处理机制
安装
bash
npm install jsonwebtoken基本使用
1. 生成 Token
javascript
const jwt = require('jsonwebtoken')
// 创建 token
const token = jwt.sign(
{ userId: 123, role: 'admin' }, // payload
'your-secret-key', // 密钥
{ expiresIn: '1h' }, // 配置选项
)2. 验证 Token
javascript
try {
const decoded = jwt.verify(token, 'your-secret-key')
console.log(decoded) // { userId: 123, role: 'admin', iat: 1516239022 }
} catch (err) {
console.error('Token 验证失败:', err.message)
}高级特性
1. 自定义声明
javascript
const token = jwt.sign(
{
sub: '1234567890',
name: 'John Doe',
admin: true,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 60 * 60,
},
'your-secret-key',
)2. 异步操作
javascript
// 异步生成 token
jwt.sign({ data: 'payload' }, 'secret', { expiresIn: '1h' }, (err, token) => {
if (err) throw err
console.log(token)
})
// 异步验证 token
jwt.verify(token, 'secret', (err, decoded) => {
if (err) throw err
console.log(decoded)
})3. 使用算法选项
javascript
const token = jwt.sign({ data: 'payload' }, 'secret', {
algorithm: 'HS256',
expiresIn: '1h',
issuer: 'auth0',
})最佳实践
安全密钥管理
javascriptconst secretKey = process.env.JWT_SECRET_KEY if (!secretKey) { throw new Error('JWT secret key is not defined') }错误处理
javascriptfunction verifyToken(token) { try { return jwt.verify(token, secretKey) } catch (err) { if (err.name === 'TokenExpiredError') { throw new Error('Token 已过期') } if (err.name === 'JsonWebTokenError') { throw new Error('无效的 Token') } throw err } }刷新 Token
javascriptfunction refreshToken(oldToken) { const decoded = jwt.verify(oldToken, secretKey, { ignoreExpiration: true, }) delete decoded.exp delete decoded.iat return jwt.sign(decoded, secretKey, { expiresIn: '1h' }) }
应用场景
1. 用户认证系统
javascript
// 登录接口
app.post('/login', (req, res) => {
const { username, password } = req.body
// 验证用户凭据
User.findOne({ username }, (err, user) => {
if (err || !user) return res.status(401).json({ message: '用户名或密码错误' })
user.comparePassword(password, (err, isMatch) => {
if (err || !isMatch) return res.status(401).json({ message: '用户名或密码错误' })
// 生成访问令牌和刷新令牌
const accessToken = jwt.sign({ userId: user._id, role: user.role }, process.env.JWT_SECRET, { expiresIn: '15m' })
const refreshToken = jwt.sign({ userId: user._id }, process.env.JWT_REFRESH_SECRET, { expiresIn: '7d' })
// 保存刷新令牌
user.refreshToken = refreshToken
user.save()
res.json({ accessToken, refreshToken })
})
})
})
// 令牌刷新接口
app.post('/refresh-token', (req, res) => {
const { refreshToken } = req.body
if (!refreshToken) return res.status(401).json({ message: '刷新令牌不存在' })
try {
// 验证刷新令牌
const decoded = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET)
// 检查数据库中的刷新令牌
User.findById(decoded.userId, (err, user) => {
if (err || !user || user.refreshToken !== refreshToken) {
return res.status(401).json({ message: '无效的刷新令牌' })
}
// 生成新的访问令牌
const accessToken = jwt.sign({ userId: user._id, role: user.role }, process.env.JWT_SECRET, { expiresIn: '15m' })
res.json({ accessToken })
})
} catch (err) {
return res.status(401).json({ message: '刷新令牌已过期或无效' })
}
})2. API 授权中间件
javascript
// 验证中间件
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization']
const token = authHeader && authHeader.split(' ')[1] // Bearer TOKEN
if (!token) return res.status(401).json({ message: '未提供访问令牌' })
jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
if (err) {
if (err.name === 'TokenExpiredError') {
return res.status(401).json({ message: '令牌已过期' })
}
return res.status(403).json({ message: '无效的令牌' })
}
req.user = decoded
next()
})
}
// 角色授权中间件
function authorizeRole(roles) {
return (req, res, next) => {
if (!req.user) return res.status(401).json({ message: '未认证' })
if (!roles.includes(req.user.role)) {
return res.status(403).json({ message: '没有权限访问此资源' })
}
next()
}
}
// 使用中间件保护路由
app.get('/api/profile', authenticateToken, (req, res) => {
res.json({ user: req.user })
})
app.get('/api/admin', authenticateToken, authorizeRole(['admin']), (req, res) => {
res.json({ message: '管理员面板' })
})常见问题与解决方案
1. 令牌泄露问题
问题: JWT 令牌被窃取后,攻击者可以访问受保护资源。
解决方案:
javascript
// 在令牌中添加指纹信息
const token = jwt.sign(
{
userId: user._id,
fingerprint: createHash(req.headers['user-agent']),
},
secretKey,
{ expiresIn: '1h' },
)
// 验证时检查指纹
function verifyTokenWithFingerprint(token, req) {
const decoded = jwt.verify(token, secretKey)
const currentFingerprint = createHash(req.headers['user-agent'])
if (decoded.fingerprint !== currentFingerprint) {
throw new Error('令牌指纹不匹配')
}
return decoded
}2. 令牌撤销问题
问题: JWT 令牌一旦签发,在过期前无法撤销。
解决方案:
javascript
// 使用令牌黑名单
const tokenBlacklist = new Set()
// 注销接口
app.post('/logout', authenticateToken, (req, res) => {
const token = req.headers.authorization.split(' ')[1]
// 将令牌添加到黑名单,可以使用 Redis 存储
tokenBlacklist.add(token)
// 设置过期时间,与令牌过期时间一致
setTimeout(() => {
tokenBlacklist.delete(token)
}, getTokenRemainingTime(token) * 1000)
res.json({ message: '注销成功' })
})
// 验证中间件中检查黑名单
function authenticateToken(req, res, next) {
const token = req.headers.authorization.split(' ')[1]
if (tokenBlacklist.has(token)) {
return res.status(401).json({ message: '令牌已被撤销' })
}
// 继续验证...
}
// 获取令牌剩余有效时间(秒)
function getTokenRemainingTime(token) {
const decoded = jwt.decode(token)
if (!decoded.exp) return 0
const now = Math.floor(Date.now() / 1000)
return Math.max(0, decoded.exp - now)
}3. 令牌大小问题
问题: JWT 令牌包含过多信息导致大小过大。
解决方案:
javascript
// 只在令牌中存储必要信息
const token = jwt.sign(
{ userId: user._id }, // 只存储标识符
secretKey,
{ expiresIn: '1h' },
)
// 需要更多信息时从数据库获取
app.get('/api/profile', authenticateToken, async (req, res) => {
try {
const user = await User.findById(req.user.userId).select('-password')
res.json({ user })
} catch (err) {
res.status(500).json({ message: '服务器错误' })
}
})注意事项
- 不要在 Token 中存储敏感信息
- 使用环境变量存储密钥
- 设置合理的过期时间
- 使用 HTTPS 传输 Token
- 定期轮换密钥
- 验证所有必要的声明(如 exp, iss 等)
- 考虑使用非对称加密算法(RS256)增强安全性
- 实现令牌刷新机制,减少长期令牌的风险
- 对关键操作使用额外的验证机制