1. 为什么需要JWT保护API
在Web应用开发中,API安全是首要考虑的问题。传统基于session的认证方式存在几个明显缺陷:
- 服务器需要存储session信息,当用户量增大时会对服务器内存造成压力
- 在分布式系统中,session共享需要额外处理
- 容易受到CSRF攻击
JWT(JSON Web Token)作为一种现代认证方案,完美解决了这些问题。我在多个生产项目中采用JWT后,发现它特别适合以下场景:
- 前后端分离架构
- 跨域认证
- 移动应用API
- 微服务间的安全通信
重要提示:JWT虽然解决了session的问题,但如果不正确使用,同样会带来安全隐患。比如未设置合理的过期时间、未验证签名等。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. JWT的工作原理与结构解析
2.1 JWT的组成结构
一个标准的JWT由三部分组成,用点(.)分隔:
code复制Header.Payload.Signature
让我们拆解一个实际例子:
code复制eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Header部分(eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9)解码后:
json复制{
"alg": "HS256",
"typ": "JWT"
}
Payload部分(eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ)解码后:
json复制{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
Signature部分是通过以下方式生成的:
javascript复制HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
2.2 JWT的认证流程
- 用户使用凭证登录
- 服务器验证凭证并生成JWT
- 将JWT返回给客户端
- 客户端在后续请求的Authorization头中携带JWT
- 服务器验证JWT并处理请求
3. 实战:实现JWT认证系统
3.1 环境准备
以Node.js为例,我们需要安装以下依赖:
bash复制npm install jsonwebtoken express body-parser
3.2 生成JWT Token
javascript复制const jwt = require('jsonwebtoken');
const secret = 'your-secret-key'; // 生产环境应使用更复杂的密钥
function generateToken(user) {
return jwt.sign(
{
userId: user.id,
username: user.username,
exp: Math.floor(Date.now() / 1000) + (60 * 60) // 1小时后过期
},
secret
);
}
3.3 验证JWT中间件
javascript复制function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) return res.sendStatus(401);
jwt.verify(token, secret, (err, user) => {
if (err) {
// 根据错误类型返回不同状态码
if (err.name === 'TokenExpiredError') {
return res.status(403).json({ error: 'Token expired' });
}
return res.sendStatus(403);
}
req.user = user;
next();
});
}
3.4 完整API示例
javascript复制const express = require('express');
const app = express();
const bodyParser = require('body-parser');
app.use(bodyParser.json());
// 模拟用户数据库
const users = [
{ id: 1, username: 'admin', password: 'admin123' }
];
// 登录路由
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.sendStatus(401);
const token = generateToken(user);
res.json({ token });
});
// 受保护的路由
app.get('/protected', authenticateToken, (req, res) => {
res.json({ message: `Hello ${req.user.username}`, user: req.user });
});
app.listen(3000, () => console.log('Server running on port 3000'));
4. JWT安全最佳实践
4.1 密钥管理
- 不要将密钥硬编码在代码中
- 使用环境变量存储密钥
- 定期轮换密钥
- 密钥长度至少256位
4.2 Token有效期管理
- 设置合理的过期时间(通常15分钟到几小时)
- 实现refresh token机制
- 提供token撤销功能
4.3 防止信息泄露
- 不要在payload中存储敏感信息
- 使用HTTPS传输
- 设置HttpOnly和Secure标志(如果存储在cookie中)
4.4 常见攻击防护
- 防止重放攻击:使用jti(JWT ID)和一次性nonce
- 防止暴力破解:限制验证失败次数
- 防止算法混淆攻击:明确指定算法
5. JWT的进阶应用
5.1 实现Refresh Token
javascript复制// 生成refresh token
function generateRefreshToken(user) {
return jwt.sign(
{
userId: user.id,
type: 'refresh'
},
secret,
{ expiresIn: '7d' }
);
}
// 刷新token的路由
app.post('/refresh', (req, res) => {
const { refreshToken } = req.body;
jwt.verify(refreshToken, secret, (err, decoded) => {
if (err || decoded.type !== 'refresh') {
return res.sendStatus(403);
}
const user = users.find(u => u.id === decoded.userId);
if (!user) return res.sendStatus(403);
const newToken = generateToken(user);
res.json({ token: newToken });
});
});
5.2 多因素认证集成
javascript复制// 生成带MFA标记的token
function generateMFAToken(user) {
return jwt.sign(
{
userId: user.id,
mfaRequired: true,
mfaVerified: false
},
secret,
{ expiresIn: '5m' }
);
}
// MFA验证后的token升级
function upgradeToken(originalToken) {
const decoded = jwt.decode(originalToken);
decoded.mfaVerified = true;
delete decoded.iat;
delete decoded.exp;
delete decoded.nbf;
delete decoded.jti;
return jwt.sign(decoded, secret);
}
5.3 微服务间的JWT传递
在微服务架构中,可以通过以下方式安全传递用户身份:
javascript复制// 网关服务验证原始JWT后生成服务间JWT
function generateServiceToken(originalToken, targetService) {
const decoded = jwt.decode(originalToken);
return jwt.sign(
{
originalUserId: decoded.userId,
service: targetService,
scope: ['read', 'write'], // 限制权限范围
exp: Math.floor(Date.now() / 1000) + 60 // 短有效期
},
serviceSecret
);
}
6. 常见问题与解决方案
6.1 Token过期处理
前端应在收到401响应后:
- 尝试使用refresh token获取新token
- 如果refresh token也过期,引导用户重新登录
- 实现自动重试机制,避免用户操作中断
6.2 多设备登录管理
- 为每个设备生成独立的refresh token
- 维护设备列表,允许用户撤销特定设备的访问权限
- 记录登录设备信息(IP、地理位置等)
6.3 性能优化
- 使用无状态验证,避免数据库查询
- 对频繁访问的API实现缓存
- 考虑使用短效token配合长效refresh token
6.4 跨域问题解决
javascript复制// Express CORS配置示例
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'https://yourdomain.com');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.header('Access-Control-Allow-Credentials', 'true');
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});
7. 实际项目中的经验分享
在大型电商平台项目中,我们遇到了几个值得分享的问题:
-
Token盗用问题:通过记录用户登录IP和User-Agent,在验证token时进行比对,发现异常立即要求重新认证。
-
秒杀场景下的性能瓶颈:对于高并发接口,我们预先生成短期有效的token,减少实时验证的开销。
-
第三方集成难题:与支付系统对接时,我们实现了双向JWT验证,确保双方API的安全调用。
-
移动端token存储:在React Native应用中,我们使用安全存储方案,避免将token直接存储在AsyncStorage中。
一个实用的调试技巧:在开发阶段,可以使用jwt.io调试器来解析和验证token,但切记不要在生产环境使用真实token测试。
