欢迎光临
我们一直在努力

JSON Web Token (JWT) 全面解析:原理、优缺点与最佳实践

文章目录

    • 什么是 JWT?
    • 1. JWT 的原理
      • A. Header (头部)
      • B. Payload (负载)
        • 1. 注册声明 (Registered claims)
        • 2. 公共声明 (Public claims)
        • 3. 私有声明 (Private claims)
      • C. Signature (签名)
      • JWT 工作流程
    • 2. JWT 的优势 (Pros)
    • 3. JWT 的劣势 (Cons)
    • 4. 最佳实践 (Best Practices)
      • 4.1 令牌生命周期管理
      • 4.2 安全存储方案对比
      • 4.3 签名算法选择
      • 4.4 完整的验证流程
    • 5. 实际代码示例
      • 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 (头部)

头部包含令牌的元数据,通常指定令牌类型和签名算法:

{
"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)

自定义声明,应避免冲突:

{
"user_id": "12345",
"role": "admin",
"permissions": ["read", "write"]
}

3. 私有声明 (Private claims)

通信双方约定的自定义声明。

⚠️ 重要提醒:Payload 只是 Base64Url 编码,未被加密!任何人都可以解码读取内容。绝不能在 Payload 中存放敏感信息(如密码)。

C. Signature (签名)

签名用于验证令牌完整性和真实性。生成方式如下:

// 使用 HS256 算法示例
signature = HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret_key
)

JWT 工作流程

#mermaid-svg-m1yijYz5S6VLMfFI {font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-m1yijYz5S6VLMfFI .error-icon{fill:#552222;}#mermaid-svg-m1yijYz5S6VLMfFI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-m1yijYz5S6VLMfFI .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-m1yijYz5S6VLMfFI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-m1yijYz5S6VLMfFI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-m1yijYz5S6VLMfFI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-m1yijYz5S6VLMfFI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-m1yijYz5S6VLMfFI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-m1yijYz5S6VLMfFI .marker.cross{stroke:#333333;}#mermaid-svg-m1yijYz5S6VLMfFI svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-m1yijYz5S6VLMfFI .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-m1yijYz5S6VLMfFI text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-m1yijYz5S6VLMfFI .actor-line{stroke:grey;}#mermaid-svg-m1yijYz5S6VLMfFI .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-m1yijYz5S6VLMfFI .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-m1yijYz5S6VLMfFI #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-m1yijYz5S6VLMfFI .sequenceNumber{fill:white;}#mermaid-svg-m1yijYz5S6VLMfFI #sequencenumber{fill:#333;}#mermaid-svg-m1yijYz5S6VLMfFI #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-m1yijYz5S6VLMfFI .messageText{fill:#333;stroke:#333;}#mermaid-svg-m1yijYz5S6VLMfFI .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-m1yijYz5S6VLMfFI .labelText,#mermaid-svg-m1yijYz5S6VLMfFI .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-m1yijYz5S6VLMfFI .loopText,#mermaid-svg-m1yijYz5S6VLMfFI .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-m1yijYz5S6VLMfFI .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-m1yijYz5S6VLMfFI .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-m1yijYz5S6VLMfFI .noteText,#mermaid-svg-m1yijYz5S6VLMfFI .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-m1yijYz5S6VLMfFI .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-m1yijYz5S6VLMfFI .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-m1yijYz5S6VLMfFI .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-m1yijYz5S6VLMfFI .actorPopupMenu{position:absolute;}#mermaid-svg-m1yijYz5S6VLMfFI .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-m1yijYz5S6VLMfFI .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-m1yijYz5S6VLMfFI .actor-man circle,#mermaid-svg-m1yijYz5S6VLMfFI line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-m1yijYz5S6VLMfFI :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

客户端

服务器

数据库

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 令牌生命周期管理

#mermaid-svg-Iz7TTPgBv857ZocI {font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Iz7TTPgBv857ZocI .error-icon{fill:#552222;}#mermaid-svg-Iz7TTPgBv857ZocI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Iz7TTPgBv857ZocI .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-Iz7TTPgBv857ZocI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Iz7TTPgBv857ZocI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Iz7TTPgBv857ZocI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Iz7TTPgBv857ZocI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Iz7TTPgBv857ZocI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Iz7TTPgBv857ZocI .marker.cross{stroke:#333333;}#mermaid-svg-Iz7TTPgBv857ZocI svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Iz7TTPgBv857ZocI .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-Iz7TTPgBv857ZocI .cluster-label text{fill:#333;}#mermaid-svg-Iz7TTPgBv857ZocI .cluster-label span{color:#333;}#mermaid-svg-Iz7TTPgBv857ZocI .label text,#mermaid-svg-Iz7TTPgBv857ZocI span{fill:#333;color:#333;}#mermaid-svg-Iz7TTPgBv857ZocI .node rect,#mermaid-svg-Iz7TTPgBv857ZocI .node circle,#mermaid-svg-Iz7TTPgBv857ZocI .node ellipse,#mermaid-svg-Iz7TTPgBv857ZocI .node polygon,#mermaid-svg-Iz7TTPgBv857ZocI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Iz7TTPgBv857ZocI .node .label{text-align:center;}#mermaid-svg-Iz7TTPgBv857ZocI .node.clickable{cursor:pointer;}#mermaid-svg-Iz7TTPgBv857ZocI .arrowheadPath{fill:#333333;}#mermaid-svg-Iz7TTPgBv857ZocI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Iz7TTPgBv857ZocI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Iz7TTPgBv857ZocI .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-Iz7TTPgBv857ZocI .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-Iz7TTPgBv857ZocI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Iz7TTPgBv857ZocI .cluster text{fill:#333;}#mermaid-svg-Iz7TTPgBv857ZocI .cluster span{color:#333;}#mermaid-svg-Iz7TTPgBv857ZocI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Iz7TTPgBv857ZocI :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

未过期

已过期

有效

无效

用户登录

生成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 签名算法选择

// 推荐:使用非对称算法 RS256
const header = {
"alg": "RS256", // RSA Signature with SHA-256
"typ": "JWT"
};

// 不推荐:对称算法 HS256(所有服务共享密钥)
const header_unsafe = {
"alg": "HS256", // 密钥泄露风险高
"typ": "JWT"
};

4.4 完整的验证流程

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 实现示例

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' });
}
}
}

安全配置示例

# 生产环境安全配置
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 的身份验证系统。


参考文档

  • RFC 7519
  • JSON Web Tokens – jwt.io
  • 赞(0)
    未经允许不得转载:171主机测评 » JSON Web Token (JWT) 全面解析:原理、优缺点与最佳实践
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址