JWT 高级主题 #

JWKS(JSON Web Key Set) #

什么是 JWKS? #

JWKS 是一组公钥的 JSON 表示,用于验证 JWT 签名。它允许客户端动态获取验证密钥。

text
┌─────────────────────────────────────────────────────────────┐
│                    JWKS 工作原理                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌─────────┐                ┌─────────┐                   │
│   │  客户端  │                │ 授权服务 │                   │
│   └────┬────┘                └────┬────┘                   │
│        │                          │                         │
│        │ 1. 获取 JWKS            │                         │
│        │─────────────────────────>│                         │
│        │                          │                         │
│        │ 2. 返回公钥集合          │                         │
│        │<─────────────────────────│                         │
│        │                          │                         │
│        │ 3. 接收 JWT             │                         │
│        │                          │                         │
│        │ 4. 从 Header 获取 kid   │                         │
│        │                          │                         │
│        │ 5. 匹配 JWKS 中的公钥   │                         │
│        │                          │                         │
│        │ 6. 验证签名             │                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

JWKS 格式 #

json
{
  "keys": [
    {
      "kty": "RSA",
      "kid": "key-2024-01",
      "use": "sig",
      "alg": "RS256",
      "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
      "e": "AQAB"
    },
    {
      "kty": "EC",
      "kid": "key-2024-02",
      "use": "sig",
      "alg": "ES256",
      "crv": "P-256",
      "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
      "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
    }
  ]
}

JWKS 字段说明 #

text
┌─────────────────────────────────────────────────────────────┐
│                    JWKS 字段说明                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  通用字段:                                                  │
│  ─────────────────────────────────────────────────────────  │
│  kty    密钥类型(RSA, EC, oct)                            │
│  kid    密钥标识符                                          │
│  use    用途(sig 签名, enc 加密)                          │
│  alg    算法                                                │
│                                                             │
│  RSA 密钥:                                                 │
│  ─────────────────────────────────────────────────────────  │
│  n      模数                                                │
│  e      公开指数                                            │
│                                                             │
│  EC 密钥:                                                  │
│  ─────────────────────────────────────────────────────────  │
│  crv    曲线(P-256, P-384, P-521)                         │
│  x      X 坐标                                              │
│  y      Y 坐标                                              │
│                                                             │
│  对称密钥:                                                  │
│  ─────────────────────────────────────────────────────────  │
│  k      密钥值(Base64URL 编码)                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

使用 JWKS 验证 JWT #

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

const client = jwksClient({
  jwksUri: 'https://auth.example.com/.well-known/jwks.json',
  cache: true,
  cacheMaxAge: 86400000,
  rateLimit: true,
  jwksRequestsPerMinute: 10
});

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

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

Python 使用 JWKS #

python
import jwt
import requests
from jwt import PyJWKClient

jwks_url = 'https://auth.example.com/.well-known/jwks.json'
jwks_client = PyJWKClient(jwks_url)

def verify_token(token):
    signing_key = jwks_client.get_signing_key_from_jwt(token)
    
    return jwt.decode(
        token,
        signing_key.key,
        algorithms=['RS256'],
        issuer='https://auth.example.com',
        audience='https://api.example.com'
    )

密钥轮换最佳实践 #

text
┌─────────────────────────────────────────────────────────────┐
│                    密钥轮换流程                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 生成新密钥                                              │
│     - 创建新的公私钥对                                      │
│     - 分配新的 kid                                          │
│                                                             │
│  2. 添加到 JWKS                                             │
│     - 新密钥添加到 keys 数组                                │
│     - JWKS 现在包含多个密钥                                 │
│                                                             │
│  3. 使用新密钥签名                                          │
│     - 新 Token 使用新密钥                                   │
│     - Header 包含新 kid                                     │
│                                                             │
│  4. 过渡期                                                  │
│     - 旧 Token 仍可用旧公钥验证                             │
│     - 新 Token 用新公钥验证                                 │
│                                                             │
│  5. 移除旧密钥                                              │
│     - 等待所有旧 Token 过期                                 │
│     - 从 JWKS 移除旧密钥                                    │
│                                                             │
│  建议轮换周期:                                              │
│  - 高安全:30 天                                            │
│  - 中安全:90 天                                            │
│  - 低安全:180 天                                           │
│                                                             │
└─────────────────────────────────────────────────────────────┘

JWE(JSON Web Encryption) #

什么是 JWE? #

JWE 用于加密 JWT 的内容,确保只有授权方可以读取 Payload。

text
┌─────────────────────────────────────────────────────────────┐
│                    JWE 结构                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  JWE 由五部分组成:                                          │
│                                                             │
│  header.encryptedKey.iv.ciphertext.tag                     │
│     │        │        │      │          │                   │
│     │        │        │      │          └── 认证标签       │
│     │        │        │      └── 加密后的内容              │
│     │        │        └── 初始化向量                       │
│     │        └── 加密的密钥                                 │
│     └── JOSE Header                                        │
│                                                             │
│  示例:                                                     │
│  eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.           │
│  OKOawDo13gRp2ojaHV7LFpZcgVbTqDVWx3HbL9N3...               │
│  48V1_ALb6US04U3b.                                         │
│  5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8 │
│  iB7j6jiSdi6IrUdNCmZzmL0.                                   │
│  XFBoMYUZodetZdvTiFvSkQ                                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

JWE 加密示例 #

javascript
const jose = require('node-jose');

async function encryptJWT(payload, publicKey) {
  const key = await jose.JWK.asKey(publicKey, 'pem');
  
  const token = await jose.JWE.createEncrypt({ 
    format: 'compact',
    fields: {
      alg: 'RSA-OAEP',
      enc: 'A256GCM'
    }
  })
  .update(JSON.stringify(payload))
  .final(key);
  
  return token;
}

async function decryptJWT(token, privateKey) {
  const key = await jose.JWK.asKey(privateKey, 'pem');
  
  const result = await jose.JWE.createDecrypt(key)
  .decrypt(token);
  
  return JSON.parse(result.payload.toString());
}

JWE vs JWS #

text
┌─────────────────────────────────────────────────────────────┐
│                    JWE vs JWS 对比                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  JWS(JSON Web Signature):                                │
│  ─────────────────────────────────────────────────────────  │
│  - 签名验证                                                 │
│  - 保证完整性                                               │
│  - 保证真实性                                               │
│  - Payload 可见                                             │
│  - 常用于授权 Token                                         │
│                                                             │
│  JWE(JSON Web Encryption):                               │
│  ─────────────────────────────────────────────────────────  │
│  - 加密内容                                                 │
│  - 保证机密性                                               │
│  - Payload 不可见                                           │
│  - 常用于敏感数据传输                                       │
│                                                             │
│  选择建议:                                                  │
│  - 一般授权:使用 JWS                                       │
│  - 敏感数据:使用 JWE 或 JWS + JWE                          │
│  - 高安全场景:嵌套 JWT(JWE 包装 JWS)                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘

嵌套 JWT #

什么是嵌套 JWT? #

嵌套 JWT 是将一个签名的 JWT(JWS)加密在另一个 JWT(JWE)中。

text
┌─────────────────────────────────────────────────────────────┐
│                    嵌套 JWT 结构                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  外层 JWE(加密):                                          │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  Header: { "alg": "RSA-OAEP", "enc": "A256GCM",     │   │
│  │           "cty": "JWT" }                            │   │
│  │                                                     │   │
│  │  Payload: 内层 JWS                                  │   │
│  └─────────────────────────────────────────────────────┘   │
│                          │                                  │
│                          ▼                                  │
│  内层 JWS(签名):                                          │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  Header: { "alg": "RS256", "typ": "JWT" }           │   │
│  │  Payload: { "sub": "user-123", ... }                │   │
│  │  Signature: ...                                     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  优势:                                                     │
│  ✅ 同时提供签名和加密                                      │
│  ✅ 保证完整性、真实性、机密性                              │
│  ✅ 适用于高安全场景                                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

创建嵌套 JWT #

javascript
const jose = require('node-jose');

async function createNestedJWT(payload, signingKey, encryptionKey) {
  const signKey = await jose.JWK.asKey(signingKey, 'pem');
  const encKey = await jose.JWK.asKey(encryptionKey, 'pem');
  
  const jws = await jose.JWS.createSign({ 
    format: 'compact',
    fields: { 
      alg: 'RS256', 
      typ: 'JWT' 
    }
  })
  .update(JSON.stringify(payload))
  .final(signKey);
  
  const jwe = await jose.JWE.createEncrypt({ 
    format: 'compact',
    fields: { 
      alg: 'RSA-OAEP', 
      enc: 'A256GCM',
      cty: 'JWT'
    }
  })
  .update(jws)
  .final(encKey);
  
  return jwe;
}

Token 绑定 #

什么是 Token 绑定? #

Token 绑定将 JWT 与特定客户端绑定,防止 Token 被盗用后在其他设备使用。

text
┌─────────────────────────────────────────────────────────────┐
│                    Token 绑定方式                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 证书绑定(Certificate Binding)                         │
│  ─────────────────────────────────────────────────────────  │
│  - 将 Token 与客户端证书绑定                                │
│  - 使用证书公钥的哈希                                       │
│  - 适用于企业环境                                           │
│                                                             │
│  2. DPoP(Demonstrating Proof of Possession)              │
│  ─────────────────────────────────────────────────────────  │
│  - 客户端生成公私钥对                                       │
│  - 每个请求用私钥签名                                       │
│  - Token 包含公钥哈希                                       │
│                                                             │
│  3. MTLS(Mutual TLS)                                      │
│  ─────────────────────────────────────────────────────────  │
│  - 双向 TLS 认证                                            │
│  - Token 与客户端证书绑定                                   │
│  - 高安全性                                                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘

DPoP 实现 #

javascript
const jose = require('node-jose');
const crypto = require('crypto');

async function generateDPoPKey() {
  const key = await jose.JWK.createKey('EC', 'P-256', { 
    use: 'sig',
    alg: 'ES256'
  });
  return key;
}

async function createDPoPProof(method, url, accessToken, key) {
  const thumbprint = await key.thumbprint('SHA-256');
  
  const header = {
    typ: 'dpop+jwt',
    alg: 'ES256',
    jwk: key.toJSON()
  };
  
  const payload = {
    htu: url,
    htm: method,
    iat: Math.floor(Date.now() / 1000),
    jti: crypto.randomBytes(16).toString('hex'),
    ath: crypto.createHash('sha256').update(accessToken).digest('base64url')
  };
  
  const proof = await jose.JWS.createSign({ 
    format: 'compact',
    fields: header
  })
  .update(JSON.stringify(payload))
  .final(key);
  
  return proof;
}

性能优化 #

缓存策略 #

javascript
const NodeCache = require('node-cache');
const jwksClient = require('jwks-rsa');

const keyCache = new NodeCache({ 
  stdTTL: 3600,
  checkperiod: 600
});

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

async function getSigningKey(kid) {
  const cacheKey = `key:${kid}`;
  let key = keyCache.get(cacheKey);
  
  if (!key) {
    const signingKey = await client.getSigningKey(kid);
    key = signingKey.publicKey || signingKey.rsaPublicKey;
    keyCache.set(cacheKey, key);
  }
  
  return key;
}

批量验证 #

javascript
async function verifyMultipleTokens(tokens) {
  const results = await Promise.allSettled(
    tokens.map(token => verifyToken(token))
  );
  
  return results.map((result, index) => ({
    token: tokens[index],
    valid: result.status === 'fulfilled',
    payload: result.status === 'fulfilled' ? result.value : null,
    error: result.status === 'rejected' ? result.reason.message : null
  }));
}

Token 大小优化 #

text
┌─────────────────────────────────────────────────────────────┐
│                    Token 大小优化建议                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 减少声明数量                                            │
│  - 只包含必要的声明                                         │
│  - 使用简短的声明名称                                       │
│  - 避免重复信息                                             │
│                                                             │
│  2. 使用引用代替完整数据                                    │
│  - 存储用户 ID 而非完整用户对象                             │
│  - 服务端按需获取详细信息                                   │
│                                                             │
│  3. 选择合适的算法                                          │
│  - HMAC 签名最短(32-64 字节)                              │
│  - ECDSA 签名中等(64-132 字节)                            │
│  - RSA 签名最长(256 字节)                                 │
│                                                             │
│  4. 压缩(可选)                                            │
│  - 使用 DEFLATE 压缩 Payload                                │
│  - 适用于大量声明                                           │
│                                                             │
│  示例对比:                                                  │
│  优化前:~800 字节                                          │
│  优化后:~300 字节                                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

JWT 调试工具 #

在线调试 #

text
┌─────────────────────────────────────────────────────────────┐
│                    JWT 调试工具                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  在线工具:                                                  │
│  - jwt.io                   官方调试器                      │
│  - jwt.ms                   Microsoft 调试器                │
│  - jwt-debugger.com         社区调试器                      │
│                                                             │
│  命令行工具:                                                │
│  - jwt-cli                  JWT 命令行工具                  │
│  - jq                       JSON 处理                      │
│                                                             │
│  浏览器扩展:                                                │
│  - JWT Inspector            Chrome 扩展                     │
│  - JWT Debugger             Firefox 扩展                    │
│                                                             │
│  注意:                                                     │
│  ⚠️ 不要将生产密钥输入在线工具                              │
│  ⚠️ 不要将敏感 Token 分享给第三方                           │
│                                                             │
└─────────────────────────────────────────────────────────────┘

命令行调试 #

bash
jwt decode eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." | cut -d'.' -f2 | base64 -d | jq

Node.js 调试脚本 #

javascript
const jwt = require('jsonwebtoken');

function debugToken(token) {
  const decoded = jwt.decode(token, { complete: true });
  
  console.log('=== JWT Debug Info ===');
  console.log('\nHeader:');
  console.log(JSON.stringify(decoded.header, null, 2));
  
  console.log('\nPayload:');
  console.log(JSON.stringify(decoded.payload, null, 2));
  
  console.log('\nSignature:');
  console.log(decoded.signature);
  
  const now = Math.floor(Date.now() / 1000);
  
  console.log('\nValidation:');
  console.log(`- Issued at: ${new Date(decoded.payload.iat * 1000).toISOString()}`);
  console.log(`- Expires at: ${new Date(decoded.payload.exp * 1000).toISOString()}`);
  console.log(`- Is expired: ${decoded.payload.exp < now}`);
  
  if (decoded.payload.nbf) {
    console.log(`- Not before: ${new Date(decoded.payload.nbf * 1000).toISOString()}`);
    console.log(`- Is valid: ${decoded.payload.nbf <= now}`);
  }
}

debugToken(process.argv[2]);

JWT 规范扩展 #

常见扩展 #

text
┌─────────────────────────────────────────────────────────────┐
│                    JWT 规范扩展                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  RFC 8725 - JWT 最佳实践                                    │
│  ─────────────────────────────────────────────────────────  │
│  - 算法选择建议                                             │
│  - 安全验证要求                                             │
│  - 常见陷阱避免                                             │
│                                                             │
│  RFC 9068 - JWT 用于 OAuth 2.0                              │
│  ─────────────────────────────────────────────────────────  │
│  - JWT 作为访问令牌的规范                                   │
│  - 标准声明定义                                             │
│  - 验证要求                                                 │
│                                                             │
│  RFC 7800 - Proof of Possession                             │
│  ─────────────────────────────────────────────────────────  │
│  - cnf 声明                                                 │
│  - 密钥绑定                                                 │
│                                                             │
│  OpenID Connect                                             │
│  ─────────────────────────────────────────────────────────  │
│  - ID Token 规范                                            │
│  - 标准声明                                                 │
│  - 用户信息                                                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘

下一步 #

现在你已经掌握了 JWT 高级主题,接下来学习 JWT 实战实现,通过完整的项目案例巩固所学知识!

最后更新:2026-03-28