跳到主要内容JWT(JSON Web Token)详解 | 极客日志编程语言Node.js大前端
JWT(JSON Web Token)详解
综述由AI生成JWT(JSON Web Token)是一种用于在各方之间安全传输信息的开放标准。它由 Header、Payload 和 Signature 三部分组成,具有无状态、自包含和跨域友好的特点。 JWT 的工作原理、结构组成、优势劣势及适用场景,涵盖了身份验证、单点登录等常见用例。同时提供了 Node.js、Python 和前端 JavaScript 的代码示例,阐述了密钥管理、存储安全、令牌过期处理等安全考量与最佳实践,帮助开发者在微服务和分布式架构中有效实施 JWT 认证方案。
moshang37 浏览 JWT(JSON Web Token)详解
什么是 JWT
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全地传输信息。JWT 是一个紧凑的、URL 安全的令牌,通常用于身份验证和信息交换。
核心特点
- 自包含:JWT 包含所有必要的信息,无需在服务器端存储会话信息
- 无状态:服务器不需要存储用户状态,适合分布式系统
- 跨域友好:可以在不同域名之间传递
- 标准化:基于 RFC 7519 标准,有良好的生态系统支持
JWT 的结构
JWT 由三部分组成,用点(.)分隔:
Header.Payload.Signature
包含令牌类型和签名算法信息:
{"alg":"HS256","typ":"JWT"}
alg:签名算法(如 HS256、RS256 等)
typ:令牌类型,通常为 "JWT"
2. Payload(载荷)
包含声明(claims),有三种类型:
标准声明(Registered Claims)
iss(issuer):签发者
sub(subject):主题
aud(audience):受众
exp(expiration time):过期时间
nbf(not before):生效时间
iat(issued at):签发时间
jti(JWT ID):JWT 唯一标识
公共声明(Public Claims)
可以自定义,但需要避免冲突
私有声明(Private Claims)
自定义的声明,用于在同意使用它们的各方之间共享信息
示例 Payload:
{"sub":"1234567890","name"
:
"John Doe"
,
"iat"
:
1516239022
,
"exp"
:
1516242622
,
"role"
:
"admin"
}
3. Signature(签名)
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
JWT 的工作原理
认证流程
- 用户登录:用户提供凭据(用户名/密码)
- 验证凭据:服务器验证用户身份
- 生成 JWT:服务器生成包含用户信息的 JWT
- 返回令牌:将 JWT 返回给客户端
- 存储令牌:客户端存储 JWT(通常存储在 localStorage 或 cookie 中)
- 发送请求:客户端在后续请求中携带 JWT
- 验证令牌:服务器验证 JWT 的有效性
- 返回资源:验证通过后返回请求的资源
流程图
客户端 服务器 | | |-- 1. 登录请求 --------->| | |-- 2. 验证凭据 | |-- 3. 生成 JWT |<-- 4. 返回 JWT ---------| | | |-- 5. 携带 JWT 请求 ------>| | |-- 6. 验证 JWT |<-- 7. 返回资源 ---------|
JWT 的优势与劣势
优势
- 无状态:服务器不需要存储会话信息
- 可扩展:适合微服务和分布式架构
- 跨域支持:可以在不同域名间使用
- 自包含:包含所有必要信息
- 标准化:基于开放标准,有良好的工具支持
劣势
- 令牌大小:比 session ID 大
- 无法撤销:在过期前无法主动失效
- 安全风险:如果密钥泄露,所有令牌都可能被伪造
- 性能考虑:每次请求都需要验证签名
JWT 的使用场景
1. 身份验证
最常见的用途,用于替代传统的 session-based 认证
2. 信息交换
3. 单点登录(SSO)
4. API 认证
5. 移动应用
JWT 的安全考虑
1. 密钥管理
2. 传输安全
- 始终使用 HTTPS
- 避免在 URL 中传递 JWT
3. 存储安全
- 避免存储在 localStorage(XSS 风险)
- 考虑使用 httpOnly cookie
- 实现 CSRF 保护
4. 令牌过期
5. 算法选择
- 使用强加密算法(如 RS256)
- 避免使用 none 算法
实际代码示例
Node.js 示例
安装依赖
生成 JWT
const jwt = require('jsonwebtoken');
const secretKey = 'your-secret-key';
const user = { id: 1, username: 'john_doe', role: 'admin' };
const token = jwt.sign({ sub: user.id, username: user.username, role: user.role }, secretKey, {
expiresIn: '1h',
issuer: 'your-app',
audience: 'your-users'
});
console.log('Generated JWT:', token);
验证 JWT
const jwt = require('jsonwebtoken');
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
jwt.verify(token, secretKey, (err, decoded) => {
if (err) {
return res.status(403).json({ error: 'Invalid token' });
}
req.user = decoded;
next();
});
};
app.get('/protected', authenticateToken, (req, res) => {
res.json({ message: 'This is a protected route', user: req.user });
});
刷新令牌实现
const refreshTokens = new Set();
const generateTokens = (user) => {
const accessToken = jwt.sign({ sub: user.id, username: user.username }, secretKey, {
expiresIn: '15m'
});
const refreshToken = jwt.sign({ sub: user.id, type: 'refresh' }, secretKey, {
expiresIn: '7d'
});
refreshTokens.add(refreshToken);
return { accessToken, refreshToken };
};
app.post('/refresh', (req, res) => {
const { refreshToken } = req.body;
if (!refreshToken || !refreshTokens.has(refreshToken)) {
return res.status(403).json({ error: 'Invalid refresh token' });
}
try {
const decoded = jwt.verify(refreshToken, secretKey);
if (decoded.type !== 'refresh') {
return res.status(403).json({ error: 'Invalid token type' });
}
const newAccessToken = jwt.sign({ sub: decoded.sub }, secretKey, {
expiresIn: '15m'
});
res.json({ accessToken: newAccessToken });
} catch (error) {
res.status(403).json({ error: 'Invalid refresh token' });
}
});
Python 示例
安装依赖
生成和验证 JWT
import jwt
import datetime
from functools import wraps
SECRET_KEY = 'your-secret-key'
def generate_token(user_id, username, role):
"""生成 JWT 令牌"""
payload = {
'sub': user_id,
'username': username,
'role': role,
'iat': datetime.datetime.utcnow(),
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
return token
def verify_token(token):
"""验证 JWT 令牌"""
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
return payload
except jwt.ExpiredSignatureError:
raise Exception('Token has expired')
except jwt.InvalidTokenError:
raise Exception('Invalid token')
def token_required(f):
"""装饰器:需要 JWT 令牌"""
@wraps(f)
def decorated(*args, **kwargs):
token = None
if 'Authorization' in request.headers:
auth_header = request.headers['Authorization']
try:
token = auth_header.split(" ")[1]
except IndexError:
return {'message': 'Token format invalid'}, 401
if not token:
return {'message': 'Token is missing'}, 401
try:
data = verify_token(token)
current_user = data
except Exception as e:
return {'message': str(e)}, 401
return f(current_user, *args, **kwargs)
return decorated
@app.route('/protected')
@token_required
def protected(current_user):
return {'message': 'This is a protected route', 'user': current_user}
前端 JavaScript 示例
存储和发送 JWT
localStorage.setItem('accessToken', token);
const apiCall = async (url, options = {}) => {
const token = localStorage.getItem('accessToken');
const config = {
...options,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
...options.headers
}
};
try {
const response = await fetch(url, config);
if (response.status === 401) {
await refreshToken();
return fetch(url, config);
}
return response;
} catch (error) {
console.error('API call failed:', error);
throw error;
}
};
const refreshToken = async () => {
const refreshToken = localStorage.getItem('refreshToken');
try {
const response = await fetch('/api/refresh', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ refreshToken })
});
if (response.ok) {
const { accessToken } = await response.json();
localStorage.setItem('accessToken', accessToken);
} else {
window.location.href = '/login';
}
} catch (error) {
console.error('Token refresh failed:', error);
window.location.href = '/login';
}
};
最佳实践
1. 安全最佳实践
- 使用强密钥(至少 256 位)
- 设置合理的过期时间
- 使用 HTTPS 传输
- 实现令牌刷新机制
- 避免在 URL 中传递敏感令牌
2. 性能优化
- 缓存公钥(使用 RS256 时)
- 使用适当的过期时间
- 考虑令牌大小对性能的影响
3. 错误处理
- 提供清晰的错误信息
- 实现优雅的令牌过期处理
- 记录安全相关事件
4. 监控和日志
常见问题与解决方案
1. 令牌过期处理
- 实现自动刷新机制
- 提供友好的重新登录提示
- 使用较长的刷新令牌
2. 跨域问题
问题:CORS 策略阻止 JWT 传输
解决方案:
- 配置正确的 CORS 策略
- 使用 withCredentials 选项
- 考虑使用 cookie 存储
3. 令牌撤销
- 维护令牌黑名单
- 使用短期令牌
- 实现服务器端会话管理
4. 安全存储
- 使用 httpOnly cookie
- 实现 CSRF 保护
- 考虑使用内存存储
5. 性能问题
- 使用 RS256 算法(公钥验证)
- 实现令牌缓存
- 优化验证逻辑
总结
JWT 是一种强大的身份验证和信息交换标准,特别适合现代 Web 应用和微服务架构。通过理解 JWT 的结构、工作原理和安全考虑,可以有效地在项目中使用 JWT。
- JWT 是自包含的、无状态的令牌
- 由 Header、Payload 和 Signature 三部分组成
- 适合分布式系统和 API 认证
- 需要仔细考虑安全性和性能
- 实现时要注意最佳实践
选择合适的认证方案需要根据具体需求和安全要求来决定。JWT 不是万能的,但在合适的场景下,它是一个优秀的选择。
相关免费在线工具
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
- JSON美化和格式化
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online