1. 为什么需要JWT保护API?
现代Web应用中,API安全是开发者面临的首要挑战之一。传统的会话认证方式(Session-Cookie)在分布式系统中暴露出明显的局限性:服务器需要维护会话状态,跨域资源共享(CORS)配置复杂,移动端兼容性差。这正是JWT(JSON Web Token)近年来成为主流方案的根本原因。
我经历过一个典型的案例:某电商平台最初采用Session存储用户状态,当业务扩展到微服务架构时,每次用户请求都需要查询中央会话数据库,导致认证延迟高达300ms。迁移到JWT后,认证时间降至5ms以内,且完美支持APP、小程序等多端统一认证。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. JWT核心机制解析
2.1 令牌结构拆解
一个标准的JWT由三部分组成,通过点号连接:
code复制Header.Payload.Signature
Header示例:
json复制{
"alg": "HS256",
"typ": "JWT"
}
这里的关键是算法选择(alg)。HS256(HMAC SHA-256)是最常用的对称加密方案,而RS256(RSA SHA-256)则采用非对称加密,更适合多服务端场景。
Payload实战技巧:
json复制{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 1516242622
}
sub(subject):用户唯一标识,建议用数据库主键exp(expiration):务必设置过期时间(通常2-4小时)- 自定义声明建议加命名空间(如
com.myapp.premium)
2.2 签名安全实践
以HS256为例,签名生成过程:
javascript复制HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
致命陷阱:曾有个项目直接用用户密码作为secret,导致密码哈希泄露后攻击者可以伪造任意令牌。正确的做法是:
- 使用32字节以上的随机字符串
- 通过环境变量注入,而非硬编码
- 定期轮换密钥(如每90天)
3. 完整实现方案
3.1 Node.js实战示例
安装依赖:
bash复制npm install jsonwebtoken cookie-parser
认证中间件:
javascript复制const jwt = require('jsonwebtoken');
const authenticate = (req, res, next) => {
const token = req.cookies.jwt ||
req.headers['x-access-token'];
if (!token) return res.status(403).send('需要认证');
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
// 区分过期和其他错误
if (err.name === 'TokenExpiredError') {
return res.status(401).send('令牌已过期');
}
return res.status(401).send('无效令牌');
}
};
3.2 令牌刷新机制
这是大多数教程忽略的关键点。推荐的双令牌方案:
- Access Token:短期(如2小时),直接包含用户信息
- Refresh Token:长期(如7天),只包含用户ID,存数据库
刷新接口实现:
javascript复制app.post('/refresh', async (req, res) => {
const refreshToken = req.body.token;
if (!refreshToken) return res.sendStatus(401);
// 验证refresh token有效性
const storedToken = await Token.findOne({
where: { token: refreshToken }
});
if (!storedToken) return res.sendStatus(403);
try {
const decoded = jwt.verify(
refreshToken,
process.env.REFRESH_SECRET
);
// 生成新的access token
const newAccessToken = jwt.sign(
{ userId: decoded.userId },
process.env.JWT_SECRET,
{ expiresIn: '2h' }
);
return res.json({ accessToken: newAccessToken });
} catch (err) {
return res.sendStatus(403);
}
});
4. 高级安全防护
4.1 常见攻击防御
CSRF防护:
- 设置SameSite=Strict的Cookie
- 关键操作要求二次认证
令牌劫持:
nginx复制# 强制HTTPS并设置安全头
add_header Strict-Transport-Security "max-age=63072000";
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
4.2 性能优化
无状态分页方案:
json复制{
"data": [...],
"next": "eyJpZCI6MjV9" // 用JWT加密最后记录ID
}
分布式验证优化:
python复制# 使用JWKS(JSON Web Key Set)替代硬编码密钥
from authlib.jose import jwt, JWKClient
jwks_client = JWKClient('https://auth.server/.well-known/jwks.json')
claims = jwt.decode(token, jwks_client.get_signing_key)
5. 生产环境踩坑记录
-
时钟偏移问题:
多服务器间时间不同步会导致立即过期的令牌验证失败。解决方案:javascript复制// 允许1分钟时钟偏移 jwt.verify(token, secret, { clockTolerance: 60 }) -
日志敏感信息:
某次事故中发现Nginx日志完整记录Authorization头。修复方案:nginx复制log_format main '$remote_addr - $request $status'; -
移动端令牌存储:
- iOS推荐Keychain
- Android推荐EncryptedSharedPreferences
- 绝对避免AsyncStorage或SharedPreferences明文存储
-
JWT长度限制:
当用户权限过多时,令牌可能超过HTTP头限制(通常8KB)。解决方案:- 关键权限实时查询
- 使用精简的权限编码(如bitmask)
在最近一次金融级应用中,我们最终采用的方案是:HS256算法 + 双令牌机制 + JWKS密钥轮换 + 关键操作二次认证。这套组合拳成功抵御了包括令牌重放、中间人攻击在内的多种安全威胁。
