示例说明
验证方不该硬编码签发方公钥——签发方需要定期换钥。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);