文章目录
-
- [什么是 JWT?](#什么是 JWT?)
- [1. JWT 的原理](#1. JWT 的原理)
-
- [A. Header (头部)](#A. Header (头部))
- [B. Payload (负载)](#B. Payload (负载))
-
- [1. 注册声明 (Registered claims)](#1. 注册声明 (Registered claims))
- [2. 公共声明 (Public claims)](#2. 公共声明 (Public claims))
- [3. 私有声明 (Private claims)](#3. 私有声明 (Private claims))
- [C. Signature (签名)](#C. Signature (签名))
- [JWT 工作流程](#JWT 工作流程)
- [2. JWT 的优势 (Pros)](#2. JWT 的优势 (Pros))
- [3. JWT 的劣势 (Cons)](#3. JWT 的劣势 (Cons))
- [4. 最佳实践 (Best Practices)](#4. 最佳实践 (Best Practices))
-
- [4.1 令牌生命周期管理](#4.1 令牌生命周期管理)
- [4.2 安全存储方案对比](#4.2 安全存储方案对比)
- [4.3 签名算法选择](#4.3 签名算法选择)
- [4.4 完整的验证流程](#4.4 完整的验证流程)
- [5. 实际代码示例](#5. 实际代码示例)
-
- [Node.js 实现示例](#Node.js 实现示例)
- 安全配置示例
- 总结
- 参考文档
什么是 JWT?
JWT (JSON Web Token),发音为 /dʒɒt/,是一种开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息(声明)。
简单来说 :JWT 就是一个经过特殊编码和签名的字符串,它本身包含了用户信息(如用户ID、角色等),服务器可以通过验证这个字符串的签名来信任其中的信息。它最常用于身份验证(Authentication) 和授权(Authorization)。
1. JWT 的原理
JWT 的核心在于它的三部分结构 。一个 JWT 令牌由三部分组成,中间用点 (.) 分隔:
Header.Payload.Signature
例如:xxxxx.yyyyy.zzzzz
A. Header (头部)
头部包含令牌的元数据,通常指定令牌类型和签名算法:
json
{
"alg": "HS256", // 签名算法 (Algorithm)
"typ": "JWT" // 令牌类型 (Type)
}
| 字段 | 说明 | 示例值 |
|---|---|---|
alg |
签名算法 | HS256, RS256 |
typ |
令牌类型 | JWT |
这部分经过 Base64Url 编码后形成 JWT 的第一部分。
B. Payload (负载)
负载包含声明(Claims) - 关于实体和其他数据的声明。声明分为三种类型:
1. 注册声明 (Registered claims)
标准预定义声明,推荐使用:
| 声明 | 全称 | 说明 | 示例 |
|---|---|---|---|
iss |
Issuer | 签发者 | "iss": "auth-server" |
sub |
Subject | 主题(用户ID) | "sub": "user-123" |
aud |
Audience | 受众 | "aud": "api.example.com" |
exp |
Expiration Time | 过期时间 | "exp": 1516242622 |
iat |
Issued At | 签发时间 | "iat": 1516239022 |
2. 公共声明 (Public claims)
自定义声明,应避免冲突:
json
{
"user_id": "12345",
"role": "admin",
"permissions": ["read", "write"]
}
3. 私有声明 (Private claims)
通信双方约定的自定义声明。
⚠️ 重要提醒 :Payload 只是 Base64Url 编码,未被加密 !任何人都可以解码读取内容。绝不能在 Payload 中存放敏感信息(如密码)。
C. Signature (签名)
签名用于验证令牌完整性和真实性。生成方式如下:
javascript
// 使用 HS256 算法示例
signature = HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret_key
)
JWT 工作流程
客户端 服务器 数据库 1. 用户名/密码登录 2. 验证凭据 3. 生成JWT(Header.Payload.Signature) 4. 返回JWT令牌 5. 携带JWT访问API (Authorization: Bearer <token>) 6. 验证签名和声明 7. 处理业务逻辑(可选) 8. 返回请求数据 返回401未授权 alt [验证成功] [验证失败] 客户端 服务器 数据库
2. JWT 的优势 (Pros)
| 优势 | 说明 | 应用场景 |
|---|---|---|
| ✅ 无状态 (Stateless) | 服务器不需存储会话信息 | 微服务架构、水平扩展 |
| ✅ 自包含 (Self-contained) | 包含用户信息,减少数据库查询 | 高性能API |
| ✅ 跨语言/平台 | 标准格式,多种语言支持 | 多技术栈系统 |
| ✅ 防篡改 | 签名确保数据完整性 | 安全敏感应用 |
3. JWT 的劣势 (Cons)
| 劣势 | 风险 | 缓解方案 |
|---|---|---|
| ❌ 无法主动失效 | 令牌泄露后无法立即撤销 | 短过期时间 + 刷新令牌 |
| ❌ Payload 内容可见 | 信息可能被解码查看 | 不存储敏感数据 |
| ❌ 令牌体积较大 | 增加网络开销 | 精简Payload内容 |
| ❌ 密钥管理复杂 | 密钥泄露导致安全风险 | 使用非对称加密 |
4. 最佳实践 (Best Practices)
4.1 令牌生命周期管理
未过期 已过期 有效 无效 用户登录 生成Access Token
有效期: 15分钟 生成Refresh Token
有效期: 7天 存储在客户端内存 存储在HttpOnly Cookie 访问受保护资源 Token是否过期? 正常访问 使用Refresh Token
获取新Access Token Refresh Token有效? 重新登录
4.2 安全存储方案对比
| 存储位置 | 安全性 | 易用性 | 推荐场景 |
|---|---|---|---|
| HttpOnly Cookie | ⭐⭐⭐⭐⭐ | ⭐⭐ | Refresh Token |
| 内存变量 | ⭐⭐⭐⭐ | ⭐⭐ | Access Token |
| sessionStorage | ⭐⭐ | ⭐⭐⭐ | 单标签页应用 |
| localStorage | ⭐ | ⭐⭐⭐⭐ | 不推荐 |
4.3 签名算法选择
javascript
// 推荐:使用非对称算法 RS256
const header = {
"alg": "RS256", // RSA Signature with SHA-256
"typ": "JWT"
};
// 不推荐:对称算法 HS256(所有服务共享密钥)
const header_unsafe = {
"alg": "HS256", // 密钥泄露风险高
"typ": "JWT"
};
4.4 完整的验证流程
javascript
function verifyJWT(token, publicKey) {
try {
// 1. 解析令牌
const [headerBase64, payloadBase64, signature] = token.split('.');
// 2. 验证签名
const signingInput = `${headerBase64}.${payloadBase64}`;
const isValidSignature = verifySignature(signingInput, signature, publicKey);
if (!isValidSignature) {
throw new Error('Invalid signature');
}
// 3. 解码Payload
const payload = JSON.parse(base64UrlDecode(payloadBase64));
// 4. 验证标准声明
const currentTime = Math.floor(Date.now() / 1000);
if (payload.exp && payload.exp < currentTime) {
throw new Error('Token expired');
}
if (payload.nbf && payload.nbf > currentTime) {
throw new Error('Token not yet valid');
}
// 5. 验证自定义业务规则
if (payload.iss !== 'trusted-issuer') {
throw new Error('Invalid issuer');
}
return payload;
} catch (error) {
console.error('JWT verification failed:', error);
return null;
}
}
5. 实际代码示例
Node.js 实现示例
javascript
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
class JWTService {
constructor() {
// 生成RSA密钥对(生产环境应从安全存储读取)
this.privateKey = process.env.JWT_PRIVATE_KEY;
this.publicKey = process.env.JWT_PUBLIC_KEY;
}
// 生成Access Token
generateAccessToken(user) {
const payload = {
sub: user.id,
name: user.name,
role: user.role,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + (15 * 60), // 15分钟过期
iss: 'my-auth-server',
aud: 'my-api-server'
};
return jwt.sign(payload, this.privateKey, { algorithm: 'RS256' });
}
// 生成Refresh Token(存储到数据库)
generateRefreshToken(user) {
const refreshToken = crypto.randomBytes(40).toString('hex');
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7天后过期
// 存储到数据库(此处简化)
this.saveRefreshToken(user.id, refreshToken, expiresAt);
return refreshToken;
}
// 验证JWT中间件
verifyTokenMiddleware(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.substring(7);
try {
const decoded = jwt.verify(token, this.publicKey, {
algorithms: ['RS256'],
issuer: 'my-auth-server',
audience: 'my-api-server'
});
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
}
}
安全配置示例
yaml
# 生产环境安全配置
security:
jwt:
algorithm: "RS256" # 使用非对称加密
access_token_expiry: "15m" # Access Token 15分钟过期
refresh_token_expiry: "7d" # Refresh Token 7天过期
cookie:
http_only: true # 防止XSS读取
secure: true # 仅HTTPS传输
same_site: "strict" # 防止CSRF
总结
JWT 是一种强大的身份验证机制,但其安全性高度依赖于正确的实现。关键要点:
- ✅ 使用短期的 Access Token 配合 Refresh Token 机制
- ✅ 优先使用 RS256 非对称加密算法
- ✅ 安全存储令牌(HttpOnly Cookie + 内存)
- ✅ 始终验证所有声明和签名
- ✅ 强制使用 HTTPS
- ❌ 避免在 Payload 中存储敏感信息
- ❌ 不要使用过长的令牌过期时间
通过遵循这些最佳实践,您可以构建既安全又高效的基于 JWT 的身份验证系统。