← 中文资源

简体中文版本

Jose JWKS 密钥轮换验证

用 jose 的远程 JWKS 集验证令牌:算法白名单防降级、冷却期内复用密钥缓存、密钥不匹配时触发刷新重试以平滑轮换。

阅读英文原文

示例说明

验证方不该硬编码签发方公钥——签发方需要定期换钥。JWKS 端点按 kid 暴露当前全部公钥,createRemoteJWKSet 把它变成可传给 jwtVerify 的密钥源:首次访问拉取并缓存,coolodown 时长内命中缓存。令牌头里的 kid 指明用哪把钥匙验证,新钥上线旧钥保留一段重叠期即可平滑轮换。

技术要点

  • algorithms 白名单拒绝算法降级攻击
  • cooldownDuration 控制密钥集缓存时长
  • NO_MATCHING_KEY 时 reload 触发即时刷新

代码解读

verifyAccessToken 把 JWKS 作为非对称密钥源传入,issuer 与 audience 双声明锁定令牌用途;命中不存在的 kid 时捕获特定错误码并 reload 强制同步最新密钥集,下一次验证自然成功。clockTolerance 吸收多机时钟偏差。

使用建议

reload 调用后应当重试一次而不是直接失败,示例将其作为异常处理钩子展示。JWKS 端点本身要走 HTTPS 并控制缓存头。旧钥的下线窗口要长于最长令牌有效期。

代码示例

import { createRemoteJWKSet, jwtVerify } from 'jose';

const JWKS_URL = 'https://auth.example.com/.well-known/jwks.json';

const JWKS = createRemoteJWKSet(new URL(JWKS_URL), {
  cooldownDuration: 30_000,
  timeoutDuration: 5000,
});

async function verifyAccessToken(token) {
  try {
    const { payload, protectedHeader } = await jwtVerify(token, JWKS, {
      algorithms: ['ES256', 'RS256'],
      issuer: 'https://auth.example.com',
      audience: 'api://billing',
      clockTolerance: 10,
    });

    return {
      subject: payload.sub,
      scope: payload.scope ?? '',
      alg: protectedHeader.alg,
    };
  } catch (err) {
    if (err.code === 'ERR_JWKS_NO_MATCHING_KEY') {
      // 轮换刚发生:强制刷新密钥集后重试一次
      JWKS.reload();
    }
    throw err;
  }
}

const claims = await verifyAccessToken(process.env.SAMPLE_TOKEN ?? 'dummy');
console.log('verified subject:', claims.subject);