环企首页
jsonwebtoken (JWT)
概述
JWT(JSON Web Token)是目前最流行的跨域身份认证方案。核心思想是:服务端不存储 Token,而是通过签名验证客户端发来的 Token 是否可信。
JWT 结构:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4ifQ.kKeG9z
^header ^payload ^signature
三部分 Base64URL 编码,用 . 连接。服务端只需验证签名,无法伪造或篡改。
适用场景:
- 前后端分离的无状态身份认证
- 跨域授权
- 单点登录(SSO)
- API 访问令牌
不适用的场景:
- 敏感信息存储(Payload 仅 Base64 编码,可被解码,不要存密码等敏感数据)
- 需要服务端强制失效的场景(用 Redis 黑名单)
核心函数
jwt.sign(payload, secretOrPrivateKey, options)
生成 Token。
const jwt = require('jsonwebtoken')
const secret = process.env.JWT_SECRET // 必须足够长且随机
// 基础用法(HS256 对称签名,最常用)
const token = jwt.sign(
{ userId: 123, role: 'admin' }, // payload(载荷)
secret, // 密钥
{ expiresIn: '7d' } // 选项
)
// payload 常用标准字段(claims)
const payload = {
sub: '1234567890', // subject(用户 ID)
name: '张三', // 自定义字段
role: 'user',
iat: Math.floor(Date.now() / 1000), // issued at(签发时间,自动添加)
exp: Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60 // 过期时间
}
// 过期时间写法
{ expiresIn: '2 hours' } // '1h', '7d', '30s', '2 days'
{ expiresIn: 3600 } // 秒数
{ expiresIn: '2 days', algorithm: 'HS256' }
// RS256 非对称签名(私钥签,公钥验,适合多服务端场景)
const privateKey = fs.readFileSync('private.pem')
const publicKey = fs.readFileSync('public.pem')
const token = jwt.sign({ userId: 123 }, privateKey, {
algorithm: 'RS256',
expiresIn: '1h'
})
jwt.verify(token, secretOrPublicKey, options)
验证并解析 Token。
// 同步验证(推荐)
try {
const decoded = jwt.verify(token, secret)
console.log(decoded.userId) // 自动解析 payload
console.log(decoded.iat) // 签发时间
console.log(decoded.exp) // 过期时间
} catch (err) {
// Token 无效(过期/伪造/格式错误)
if (err.name === 'TokenExpiredError') {
// Token 过期
} else if (err.name === 'JsonWebTokenError') {
// 签名不匹配或格式错误
} else if (err.name === 'NotBeforeError') {
// Token 尚未生效
}
}
// 异步验证
jwt.verify(token, secret, (err, decoded) => {
if (err) return res.status(401).json({ error: '无效 Token' })
req.user = decoded
})
jwt.decode(token, options)
仅解析 Token(不验证签名),仅用于读取 Payload。
// 不验证签名,直接解析(适合调试)
const decoded = jwt.decode(token)
// 仅获取 header
const header = jwt.decode(token, { complete: true }).header
// 适用于验证格式但不验证签名的场景(如从 URL 参数解析)
完整使用流程
1. 签发 Token(登录时)
app.post('/login', async (req, res) => {
const { username, password } = req.body
const user = await db.findUser(username)
if (!user || !await bcrypt.compare(password, user.passwordHash)) {
return res.status(401).json({ error: '用户名或密码错误' })
}
const token = jwt.sign(
{
userId: user.id,
role: user.role,
name: user.name
},
process.env.JWT_SECRET,
{ expiresIn: '7d' }
)
res.json({ token })
})
2. 验证 Token(中间件)
// 方式1:手动解析(推荐,方便控制)
const authMiddleware = (req, res, next) => {
const authHeader = req.headers['authorization']
if (!authHeader) {
return res.status(401).json({ error: '未提供 Token' })
}
const token = authHeader.replace('Bearer ', '')
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET)
req.user = decoded // 后续路由可用
next()
} catch (err) {
if (err.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Token 已过期,请重新登录' })
}
return res.status(401).json({ error: '无效的 Token' })
}
}
// 方式2:用 express-jwt 库(自动中间件)
const { expressjwt: jwt } = require('express-jwt')
const jwtMiddleware = jwt({
secret: process.env.JWT_SECRET,
algorithms: ['HS256'],
requestProperty: 'auth' // 解析后挂载到 req.auth
})
3. 受保护路由
app.get('/profile', authMiddleware, (req, res) => {
res.json({ user: req.user })
})
// 管理员专属
app.delete('/admin/user/:id', authMiddleware, (req, res) => {
if (req.user.role !== 'admin') {
return res.status(403).json({ error: '权限不足' })
}
// 执行删除
})
// 刷新 Token(过期前检查)
app.post('/refresh-token', authMiddleware, (req, res) => {
const newToken = jwt.sign(
{ userId: req.user.userId },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
)
res.json({ token: newToken })
})
Token 安全策略
// 1. HTTPS 传输(生产必须)
// 2. 设置合理的过期时间
const token = jwt.sign({ ... }, secret, { expiresIn: '15m' }) // 访问令牌(短)
const refreshToken = jwt.sign({ ... }, secret, { expiresIn: '7d' }) // 刷新令牌(长)
// 3. Token 黑名单(强制失效)
const blacklist = new Set()
app.post('/logout', authMiddleware, (req, res) => {
blacklist.add(req.token)
res.json({ success: true })
})
// 中间件检查黑名单
if (blacklist.has(req.token)) {
return res.status(401).json({ error: 'Token 已失效' })
}
// 4. 密钥管理
// 永远不要硬编码密钥
// 生产环境使用环境变量
// 定期轮换密钥(注意旧 Token 失效问题)
常见问题
| 问题 | 解决方案 |
|---|---|
| Token 过期后仍然可用 | Token 是无状态的,只能通过检查 exp 字段验证 |
| 想让 Token 立即失效 | 将 Token 加入 Redis 黑名单(带 TTL) |
| 多服务器共享验证 | 所有服务器用相同的 JWT_SECRET |
| 密钥泄露 | 立即更换 JWT_SECRET,通知用户重新登录 |
| Payload 被篡改 | 不可能,签名验证会失败 |
| Payload 能被看到 | 对,Payload 仅 Base64 编码,敏感数据不要放进去 |