JWT 与 OAuth #

什么是 JWT? #

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全地传输信息。JWT 是一个紧凑的、URL 安全的方式,用于表示要在双方之间传输的声明(claims)。

text
┌─────────────────────────────────────────────────────────────┐
│                    JWT 特点                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ✅ 紧凑                                                    │
│     - 体积小,可通过 URL 传输                               │
│     - 可放入 HTTP Header                                    │
│                                                             │
│  ✅ 自包含                                                  │
│     - 包含用户信息                                          │
│     - 无需查询数据库                                        │
│                                                             │
│  ✅ 可验证                                                  │
│     - 数字签名保证完整性                                    │
│     - 可验证发送者身份                                      │
│                                                             │
│  ✅ 跨语言                                                  │
│     - 所有主流语言都支持                                    │
│     - 标准化格式                                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

JWT 结构 #

三部分组成 #

text
┌─────────────────────────────────────────────────────────────┐
│                    JWT 结构                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  xxxxx.yyyyy.zzzzz                                          │
│    │       │      │                                         │
│    │       │      └── Signature(签名)                     │
│    │       └───────── Payload(载荷)                       │
│    └───────────────── Header(头部)                        │
│                                                             │
│  示例:                                                     │
│  eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.                      │
│  eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0   │
│  IjoxNTE2MjM5MDIyfQ.                                        │
│  SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c                │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Header(头部) #

json
{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "key-id-123"
}

字段说明:

字段 描述
alg 签名算法(HS256, RS256, ES256 等)
typ 令牌类型,通常为 JWT
kid 密钥标识符(可选)

Payload(载荷) #

json
{
  "iss": "https://auth.example.com",
  "sub": "user-123",
  "aud": "client-app",
  "exp": 1516239022,
  "iat": 1516239022,
  "nbf": 1516239022,
  "jti": "unique-token-id",
  "scope": "profile email",
  "name": "John Doe",
  "email": "john@example.com"
}

标准声明(Registered Claims):

声明 名称 描述
iss Issuer 签发者
sub Subject 主题(用户标识)
aud Audience 受众(接收方)
exp Expiration Time 过期时间
iat Issued At 签发时间
nbf Not Before 生效时间
jti JWT ID 唯一标识符

Signature(签名) #

text
签名过程:

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

或

RS256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  privateKey
)

JWT 签名算法 #

对称算法(HMAC) #

text
┌─────────────────────────────────────────────────────────────┐
│                    HMAC 算法                                 │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  算法:HS256, HS384, HS512                                  │
│                                                             │
│  特点:                                                     │
│  - 使用相同的密钥签名和验证                                 │
│  - 计算速度快                                               │
│  - 密钥必须安全分发                                         │
│                                                             │
│  适用场景:                                                 │
│  - 单一授权服务器                                           │
│  - 内部服务间通信                                           │
│                                                             │
│  风险:                                                     │
│  - 密钥泄露后所有令牌都不安全                               │
│  - 多个服务需要共享密钥                                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘

非对称算法(RSA/ECDSA) #

text
┌─────────────────────────────────────────────────────────────┐
│                   RSA/ECDSA 算法                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  算法:RS256, RS384, RS512, ES256, ES384, ES512             │
│                                                             │
│  特点:                                                     │
│  - 私钥签名,公钥验证                                       │
│  - 公钥可以公开分发                                         │
│  - 计算较慢                                                 │
│                                                             │
│  适用场景:                                                 │
│  - 多个资源服务器                                           │
│  - 公开的 API                                               │
│  - OpenID Connect                                           │
│                                                             │
│  优势:                                                     │
│  - 资源服务器只需公钥                                       │
│  - 私钥只在授权服务器                                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

JWT 在 OAuth 中的应用 #

作为访问令牌 #

text
┌─────────────────────────────────────────────────────────────┐
│                  JWT 作为访问令牌                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  优势:                                                     │
│  ✅ 无状态验证                                              │
│     - 资源服务器无需查询数据库                              │
│     - 直接验证签名即可                                      │
│                                                             │
│  ✅ 自包含信息                                              │
│     - 包含用户标识                                          │
│     - 包含权限范围                                          │
│     - 包含过期时间                                          │
│                                                             │
│  ✅ 分布式友好                                              │
│     - 多个资源服务器可独立验证                              │
│     - 无需共享会话存储                                      │
│                                                             │
│  劣势:                                                     │
│  ⚠️ 无法主动撤销                                            │
│  ⚠️ 令牌体积较大                                            │
│  ⚠️ 敏感信息不应放入                                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

访问令牌示例 #

json
{
  "iss": "https://auth.example.com",
  "sub": "user-123",
  "aud": "https://api.example.com",
  "exp": 1516239022,
  "iat": 1516239022,
  "jti": "unique-token-id",
  "client_id": "client-app",
  "scope": "profile email read:orders",
  "tenant_id": "tenant-001"
}

作为 ID Token(OpenID Connect) #

json
{
  "iss": "https://auth.example.com",
  "sub": "user-123",
  "aud": "client-app",
  "exp": 1516239022,
  "iat": 1516239022,
  "auth_time": 1516239000,
  "nonce": "n-0S6_WzA2Mj",
  "acr": "urn:mace:incommon:iap:silver",
  "amr": ["pwd", "mfa"],
  "azp": "client-app",
  "name": "John Doe",
  "email": "john@example.com",
  "email_verified": true,
  "picture": "https://example.com/john.jpg"
}

JWT 验证 #

验证步骤 #

text
┌─────────────────────────────────────────────────────────────┐
│                    JWT 验证步骤                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 解析 JWT                                                │
│     - 分割 Header、Payload、Signature                       │
│     - Base64URL 解码                                        │
│                                                             │
│  2. 验证签名                                                │
│     - 获取签名密钥/公钥                                     │
│     - 重新计算签名                                          │
│     - 比对签名是否一致                                      │
│                                                             │
│  3. 验证声明                                                │
│     - iss:签发者是否正确                                   │
│     - aud:受众是否包含自己                                 │
│     - exp:是否已过期                                       │
│     - nbf:是否已生效                                       │
│     - iat:签发时间是否合理                                 │
│                                                             │
│  4. 验证其他约束                                            │
│     - scope:权限是否足够                                   │
│     - 自定义声明                                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Node.js 验证示例 #

javascript
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');

const client = jwksClient({
  jwksUri: 'https://auth.example.com/.well-known/jwks.json'
});

function getKey(header, callback) {
  client.getSigningKey(header.kid, (err, key) => {
    const signingKey = key.publicKey || key.rsaPublicKey;
    callback(null, signingKey);
  });
}

async function verifyToken(token) {
  return new Promise((resolve, reject) => {
    jwt.verify(token, getKey, {
      issuer: 'https://auth.example.com',
      audience: 'client-app',
      algorithms: ['RS256']
    }, (err, decoded) => {
      if (err) {
        reject(err);
      } else {
        resolve(decoded);
      }
    });
  });
}

const decoded = await verifyToken(accessToken);
console.log(decoded);

Python 验证示例 #

python
import jwt
import requests
from cryptography.hazmat.primitives import serialization

def get_public_key(jwks_url, kid):
    jwks = requests.get(jwks_url).json()
    for key in jwks['keys']:
        if key['kid'] == kid:
            return jwt.algorithms.RSAAlgorithm.from_jwk(key)
    return None

def verify_token(token, jwks_url, issuer, audience):
    header = jwt.get_unverified_header(token)
    public_key = get_public_key(jwks_url, header['kid'])
    
    return jwt.decode(
        token,
        public_key,
        algorithms=['RS256'],
        issuer=issuer,
        audience=audience
    )

decoded = verify_token(
    access_token,
    'https://auth.example.com/.well-known/jwks.json',
    'https://auth.example.com',
    'client-app'
)
print(decoded)

JWKS(JSON Web Key Set) #

什么是 JWKS? #

text
┌─────────────────────────────────────────────────────────────┐
│                    JWKS 说明                                 │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  JWKS 是一组公钥的 JSON 表示                                │
│                                                             │
│  端点示例:                                                 │
│  https://auth.example.com/.well-known/jwks.json             │
│                                                             │
│  响应示例:                                                 │
│  {                                                          │
│    "keys": [                                                │
│      {                                                      │
│        "kty": "RSA",                                        │
│        "kid": "key-id-123",                                 │
│        "use": "sig",                                        │
│        "alg": "RS256",                                      │
│        "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQ...",           │
│        "e": "AQAB"                                          │
│      }                                                      │
│    ]                                                        │
│  }                                                          │
│                                                             │
│  字段说明:                                                 │
│  - kty:密钥类型(RSA, EC)                                 │
│  - kid:密钥标识符                                          │
│  - use:用途(sig 签名, enc 加密)                          │
│  - alg:算法                                                │
│  - n:RSA 模数                                              │
│  - e:RSA 指数                                              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

密钥轮换 #

text
┌─────────────────────────────────────────────────────────────┐
│                    密钥轮换策略                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  为什么需要密钥轮换?                                       │
│  - 降低密钥泄露风险                                         │
│  - 符合安全合规要求                                         │
│  - 定期更新最佳实践                                         │
│                                                             │
│  轮换流程:                                                 │
│                                                             │
│  1. 生成新密钥                                              │
│     - 生成新的公私钥对                                      │
│     - 添加到 JWKS(新 kid)                                 │
│                                                             │
│  2. 使用新密钥签名                                          │
│     - 新令牌使用新密钥                                      │
│     - JWKS 包含新旧密钥                                     │
│                                                             │
│  3. 过渡期                                                  │
│     - 旧令牌仍可用旧公钥验证                                │
│     - 新令牌用新公钥验证                                    │
│                                                             │
│  4. 移除旧密钥                                              │
│     - 所有旧令牌过期后                                      │
│     - 从 JWKS 移除旧密钥                                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

JWT 安全最佳实践 #

安全建议 #

text
┌─────────────────────────────────────────────────────────────┐
│                    JWT 安全最佳实践                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 使用强签名算法                                          │
│     ✅ RS256, ES256                                         │
│     ⚠️ HS256(密钥管理困难)                                │
│     ❌ none, HS256 弱密钥                                   │
│                                                             │
│  2. 验证所有声明                                            │
│     - iss(签发者)                                         │
│     - aud(受众)                                           │
│     - exp(过期时间)                                       │
│                                                             │
│  3. 不要存储敏感信息                                        │
│     ❌ 密码                                                 │
│     ❌ 信用卡号                                             │
│     ❌ 身份证号                                             │
│                                                             │
│  4. 使用短期令牌                                            │
│     - 访问令牌:15分钟-1小时                                │
│     - ID Token:短有效期                                    │
│                                                             │
│  5. 安全存储                                                │
│     - 不要存储在 localStorage                               │
│     - 使用 HttpOnly Cookie                                  │
│                                                             │
│  6. 实现 Token 撤销机制                                     │
│     - 黑名单                                                │
│     - 短有效期 + 刷新令牌                                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

常见攻击与防护 #

text
┌─────────────────────────────────────────────────────────────┐
│                    JWT 攻击与防护                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 算法混淆攻击                                            │
│     攻击:将 RS256 改为 HS256                               │
│     防护:明确指定允许的算法                                │
│                                                             │
│  2. none 算法攻击                                           │
│     攻击:使用 alg: none 绕过签名                           │
│     防护:拒绝 none 算法                                    │
│                                                             │
│  3. 弱密钥攻击                                              │
│     攻击:暴力破解弱密钥                                    │
│     防护:使用足够强度的密钥                                │
│                                                             │
│  4. 密钥泄露                                                │
│     攻击:获取密钥后伪造令牌                                │
│     防护:安全存储密钥,定期轮换                            │
│                                                             │
│  5. 时间攻击                                                │
│     攻击:操纵时间相关声明                                  │
│     防护:使用时钟同步,允许合理偏差                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

JWT vs 不透明令牌 #

特性 JWT 不透明令牌
验证方式 本地验证签名 查询授权服务器
信息存储 自包含 需要查询
撤销 困难 容易
性能 高(无网络请求) 低(需要查询)
体积 较大 较小
适用场景 分布式系统 集中式系统

参考令牌 + JWT 混合方案 #

text
┌─────────────────────────────────────────────────────────────┐
│                   混合令牌方案                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  方案:                                                     │
│  1. 授权服务器颁发不透明访问令牌                            │
│  2. 资源服务器使用令牌获取 JWT                              │
│  3. 资源服务器缓存 JWT                                      │
│                                                             │
│  流程:                                                     │
│  ┌─────────┐     ┌─────────┐     ┌─────────┐              │
│  │  客户端  │────>│ 资源服务 │────>│ 授权服务│              │
│  │         │     │         │     │         │              │
│  │         │     │ 验证令牌 │     │ 返回JWT │              │
│  │         │     │ 缓存JWT │     │         │              │
│  └─────────┘     └─────────┘     └─────────┘              │
│                                                             │
│  优势:                                                     │
│  ✅ 可以立即撤销                                            │
│  ✅ 减少授权服务器负载(缓存)                              │
│  ✅ JWT 信息可用                                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

下一步 #

现在你已经了解了 JWT 在 OAuth 中的应用,接下来学习 OpenID Connect,了解如何使用 OAuth 实现身份认证!

最后更新:2026-03-28