- 网络安全
- 认证鉴权
- 后端
【免费下载链接】jose
JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes
JWTVerifyResult是 jose 库中jwtVerify()验证签名 JWT 后返回的结果类型,封装了 JWT 验证成功后的两大核心产物——JWT Claims Set(载荷)与 JWS Protected Header(受保护头部)。本文基于 jose 仓库的接口定义(docs/types/interfaces/JWTVerifyResult.md)与 src/jwt/verify.ts 的源码实现,讲解该接口的类型结构、字段语义、泛型用法、动态密钥解析时的扩展行为,以及与之配套的 Claims 校验选项与错误处理,帮助你准确消费jwtVerify()的返回值并在 TypeScript 中获得最佳类型推断。
一、接口概览:一次验证,两个产物
在 jose 中,jwtVerify()用于"验证 JWT 格式(必须是 JWS Compact 格式)、验证 JWS 签名、校验 JWT Claims Set"。验证成功后返回Promise<JWTVerifyResult<PayloadType>>,其完整定义位于 src/types.d.ts:
export interface JWTVerifyResult<PayloadType = JWTPayload> { /** JWT Claims Set. */ payload: PayloadType & JWTPayload & ([PayloadType] extends [object] ? unknown : unknown extends PayloadType ? unknown : never) /** JWS Protected Header. */ protectedHeader: JWTHeaderParameters }该接口只有两个属性:
| 属性 | 类型 | 含义 |
|---|---|---|
payload | PayloadType & JWTPayload | JWT Claims Set(JWT 载荷) |
protectedHeader | JWTHeaderParameters | JWS Protected Header(受保护头部) |
对照 jose 中其他验证结果的同类设计:CompactVerifyResult返回的是payload: Uint8Array与protectedHeader: CompactJWSHeaderParameters(见 src/types.d.ts)。JWTVerifyResult之所以不同,是因为 JWT 的载荷是经过 Base64URL 解码并 JSON 解析后的 Claims Set 对象,而非原始字节;同时其头部必须是包含alg的 Compact 形式。
二、payload 属性:泛型化的 JWT Claims Set
payload是验证完成后解析出的 JWT 载荷对象。它的类型是PayloadType & JWTPayload,即"调用方声明的载荷类型"与"jose 内置的 JWTPayload 识别成员"的交集。
2.1 内置 JWTPayload 识别成员
JWTPayload接口(docs/types/interfaces/JWTPayload.md,定义见 src/types.d.ts)声明了 RFC 7519 中七个标准注册 Claims:
| 属性 | 类型 | RFC 定义 |
|---|---|---|
iss? | string | JWT Issuer(签发者) |
sub? | string | JWT Subject(主体) |
aud? | string \| string[] | JWT Audience(受众) |
exp? | number | JWT Expiration Time(过期时间,NumericDate) |
nbf? | number | JWT Not Before(生效时间,NumericDate) |
iat? | number | JWT Issued At(签发时间,NumericDate) |
jti? | string | JWT ID(令牌唯一标识) |
此外JWTPayload带有索引签名[propName: string]: unknown,意味着令牌中携带的任何其他自定义 Claims 也都会被保留在payload对象上。
2.2 通过 PayloadType 获得自定义 Claims 的类型推断
JWTVerifyResult是泛型接口,PayloadType默认值为JWTPayload。在验证你自己的令牌时,可以声明预期的 Claims 结构,让payload获得精确的类型提示:
interface MyClaims { role: 'admin' | 'user' org: string } const { payload } = await jose.jwtVerify<MyClaims>(jwt, secret) // payload.role 已被推断为 'admin' | 'user' // payload.org 已被推断为 string类型参数的完整说明:PayloadType表示"该令牌预期携带的 JWT Claims Set 类型定义",默认类型为JWTPayload。jose 通过交叉类型PayloadType & JWTPayload保证无论自定义声明是否覆盖,标准 Claims 的类型始终可用。
三、protectedHeader 属性:JWS Protected Header
protectedHeader是 JWT 的 JWS Protected Header(即 Compact JWS 序列化中第一段 Base64URL 解码后的对象),类型为JWTHeaderParameters。
JWTHeaderParameters(docs/types/interfaces/JWTHeaderParameters.md,定义见 src/types.d.ts)继承自CompactJWSHeaderParameters,后者要求alg字段必须存在(src/types.d.ts),并在此基础上增加了 JWS 扩展参数b64(RFC 7797 的未编码载荷选项)。其可识别成员包括:
| 属性 | 类型 | 含义 |
|---|---|---|
alg | string(必选) | JWS "alg"(Algorithm)头部参数,如HS256、RS256 |
b64? | boolean | RFC 7797 定义的 JWS 载荷表示与签名输入计算扩展 |
crit? | string[] | JWS "crit"(Critical)头部参数 |
kid? | string | "kid"(Key ID)头部参数 |
jku? | string | "jku"(JWK Set URL)头部参数 |
jwk? | Omit<JWK, ...> | "jwk"(JSON Web Key)头部参数,仅允许公钥成员 |
typ? | string | "typ"(Type)头部参数 |
cty? | string | "cty"(Content Type)头部参数 |
x5u?/x5c?/x5t? | string/string[]/string | X.509 相关头部参数 |
与JWTPayload一样,JWTHeaderParameters也带索引签名,令牌中任何其他头部成员都会保留。实践中常通过protectedHeader.alg得知签名算法、通过protectedHeader.kid在 JWKS 中定位用于验签的密钥。
四、源码视角:jwtVerify 如何构建 JWTVerifyResult
jwtVerify()的实现位于 src/jwt/verify.ts,返回值的组装逻辑非常清晰:
export async function jwtVerify(jwt, key, options?) { const verified = await verifyCompact(jwt, prepareVerify(options), key) if (!verified[2]) { throw new JWTInvalid('JWTs MUST NOT use unencoded payload') } const payload = validateClaimsSet(verified[1], verified[0], options) const result = { payload, protectedHeader: verified[1] as types.JWTHeaderParameters } if (typeof key === 'function') { return { ...result, key: verified[3] } } return result }整个调用链可以拆解为三步:
- JWS 签名验证:
verifyCompact()(位于 src/lib/jws_verify.ts)完成 JWT 格式检查与 JWS 签名验证,返回[payload, protectedHeader, isUnencodedPayload, key]元组; - 拒绝未编码载荷:若令牌使用了 RFC 7797 的未编码载荷(
b64: false),jwtVerify会直接抛出JWTInvalid,因为 JWT 不允许未编码的 Payload; - Claims Set 校验与解析:
validateClaimsSet()(位于 src/lib/jwt_claims_set.ts)将载荷字节严格解码为 UTF-8 并 JSON 解析,校验其必须是顶层 JSON 对象(否则抛JWTInvalid),随后执行iss/sub/aud/nbf/exp/iat/typ/requiredClaims等全部 Claims 校验,最后将对象作为payload返回。
需要特别注意的是,JWTVerifyResult的payload是校验通过后的 Claims Set,而非原始字节——这意味着验证失败时函数不会返回该结果,而是抛出对应错误(详见下文第七节)。
五、动态密钥解析:返回值中的第三个可选字段 key
jwtVerify()提供了三种重载(docs/jwt/verify/functions/jwtVerify.md):
- 直接传入密钥
key: KeyInput,返回JWTVerifyResult<PayloadType>(仅payload与protectedHeader); - 传入密钥解析函数
getKey: JWTVerifyGetKey<KeyType>,返回JWTVerifyResult<PayloadType> & ResolvedKey<KeyType>,即额外携带key字段; - 传入"可能是密钥也可能是解析函数"的值(用于转发场景),返回
JWTVerifyResult<PayloadType> & Partial<ResolvedKey>——此时key仅在传入了解析函数时才会出现在结果上。
ResolvedKey接口(docs/types/interfaces/ResolvedKey.md,定义见 src/types.d.ts)只有一个字段:
export interface ResolvedKey<KeyType extends CryptoKey | Uint8Array = CryptoKey | Uint8Array> { /** Key resolved from the key resolver function. */ key: KeyType }动态密钥解析函数JWTVerifyGetKey(docs/jwt/verify/interfaces/JWTVerifyGetKey.md)签名如下:
(protectedHeader: CompactJWSHeaderParameters, token: FlattenedJWSInput) => JWK | KeyObject | KeyType | Promise<JWK | KeyObject | KeyType>调用时需要注意:该函数被调用时令牌的任何组件都尚未被验证,因此不能信任函数接收到的protectedHeader与token内容;若无法为令牌匹配到合适的密钥,应抛出错误而非返回不匹配的密钥。通过收窄KeyType(例如解析函数被声明为只返回CryptoKey),可以让返回值中的key字段在调用点被精确推断。createRemoteJWKSet(docs/jwks/remote/functions/createRemoteJWKSet.md)、createLocalJWKSet(docs/jwks/local/functions/createLocalJWKSet.md)与EmbeddedJWK(docs/jwk/embedded/functions/EmbeddedJWK.md)都是这种解析函数的现成实现。
六、实战:三种典型调用方式与结果消费
jwtVerify()的完整使用示例见 docs/jwt/verify/functions/jwtVerify.md,以下三种场景涵盖了结果对象{ payload, protectedHeader }的典型消费方式。
6.1 对称密钥(HS256)
const secret = new TextEncoder().encode( 'cc7e0d44fd473002f1c42167459001140ec6389b7353f8088f4d9a95f2f596f2', ) const jwt = 'eyJhbGciOiJIUzI1NiJ9.eyJ1cm46ZXhhbXBsZTpjbGFpbSI6dHJ1ZSwiaWF0IjoxNjY5MDU2MjMxLCJpc3MiOiJ1cm46ZXhhbXBsZTppc3N1ZXIiLCJhdWQiOiJ1cm46ZXhhbXBsZTphdWRpZW5jZSJ9.C4iSlLfAUMBq--wnC6VqD9gEOhwpRZpoRarE0m7KEnI' const { payload, protectedHeader } = await jose.jwtVerify(jwt, secret, { issuer: 'urn:example:issuer', audience: 'urn:example:audience', }) console.log(protectedHeader) // { alg: 'HS256' } console.log(payload) // 校验通过后的 Claims Set6.2 公钥验签(RS256,SPKI 与 JWK 两种密钥载体)
const alg = 'RS256' const spki = `-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwhYOFK2Ocbbpb/zVypi9 ... -----END PUBLIC KEY-----` const publicKey = await jose.importSPKI(spki, alg) // 也可以使用等价的 JWK 载体: // const publicKey = await jose.importJWK({ kty: 'RSA', n: '...', e: 'AQAB' }, alg) const { payload, protectedHeader } = await jose.jwtVerify(jwt, publicKey, { issuer: 'urn:example:issuer', audience: 'urn:example:audience', })其中importSPKI与importJWK的用法可参考 docs/key/import/functions/importSPKI.md 与 docs/key/import/functions/importJWK.md。
6.3 远程 JWKS 动态解析(含 key 字段)
const JWKS = jose.createRemoteJWKSet(new URL('https://www.googleapis.com/oauth2/v3/certs')) const { payload, protectedHeader, key } = await jose.jwtVerify(jwt, JWKS, { issuer: 'urn:example:issuer', audience: 'urn:example:audience', }) console.log(protectedHeader) console.log(payload) console.log(key) // 本次解析出的实际验签密钥,仅使用解析函数时存在七、配套理解:校验选项与错误路径
要正确理解JWTVerifyResult,还需知道哪些校验失败会导致不产生该结果。jwtVerify()的options类型为JWTVerifyOptions(docs/jwt/verify/interfaces/JWTVerifyOptions.md),它是 JWS 验证选项与 JWT Claims 校验选项的组合:
| 选项 | 类型 | 行为 |
|---|---|---|
algorithms? | string[] | 允许的alg值白名单;默认允许该密钥可用的全部算法。注意:alg: none的未受保护 JWT 永远不会被此 API 接受 |
issuer? | string \| string[] | 期望的issClaim 值,设置后issClaim 变为必须存在 |
audience? | string \| string[] | 期望的audClaim 值,设置后audClaim 变为必须存在 |
subject? | string | 期望的subClaim 值,设置后subClaim 变为必须存在 |
maxTokenAge? | string \| number | 从iat起算的最大存活时间(数字表示秒,字符串如"5 seconds"、"10 minutes"、"2 hours"),设置后iat变为必须存在 |
requiredClaims? | string[] | 必须存在于 Claims Set 中的 Claim 名称数组 |
clockTolerance? | string \| number | 时钟偏移容忍秒数,用于nbf/exp以及maxTokenAge下的iat校验 |
currentDate? | Date | 比较 NumericDate Claims 时使用的基准时间,默认new Date() |
crit? | { [name: string]: boolean } | 声明的 crit 头部参数映射(true表示必须受完整性保护) |
typ? | string | 期望的typ头部参数值,设置后typ变为必须存在 |
这些校验在validateClaimsSet()中逐项执行,失败时抛出的错误类型(定义见 docs/util/errors/README.md)包括:
JWSInvalid/JWSSignatureVerificationFailed:签名格式或验签失败;JWTInvalid:Claims Set 不是顶层 JSON 对象、或 JWT 使用了未编码载荷;JWTClaimValidationFailed:iss/sub/aud值不匹配、必需 Claim 缺失、nbf校验失败等;JWTExpired:exp已过期,或maxTokenAge下iat距今过久(docs/util/errors/classes/JWTExpired.md)。
因此,JWTVerifyResult的payload与protectedHeader可以放心地直接用于业务逻辑——它们只会在格式、签名与全部配置的 Claims 校验全部通过后出现。
八、小结
JWTVerifyResult是 jose 中 JWT 验证链路的最末端产物,payload与protectedHeader分别代表了"校验通过的 Claims Set"与"签名头部",泛型PayloadType让自定义 Claims 获得精确类型,而ResolvedKey的交叉类型则让动态密钥解析场景能够同时获知实际验签密钥。理解它的结构与生成路径,是正确使用 docs/jwt/verify/functions/jwtVerify.md 以及测试用例(test/jwt/verify.test.ts)中各类断言的前提。
- 网络安全
- 认证鉴权
- 后端
【免费下载链接】jose
JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes
相关推荐
jose 中 CompactVerifyResult 接口完全指南:理解紧凑 JWS 校验的返回值结构
jose 中 CompactVerifyResult 接口完全指南:理解紧凑 JWS 校验的返回值结构 CompactVerifyResult 是 jose 库
网络安全认证鉴权后端jose JWT 验证实战:jwtVerify 的签名验证与 Claims Set 校验全指南
jose JWT 验证实战:jwtVerify 的签名验证与 Claims Set 校验全指南 导读 本指南聚焦 jose 库中 JWT(JSON Web To
网络安全认证鉴权后端jose 中 jwtVerify 函数完全指南:JWT 签名验证与 Claims Set 校验实战
jose 中 jwtVerify 函数完全指南:JWT 签名验证与 Claims Set 校验实战 jose 项目为 Node.js、浏览器、Cloudflar
网络安全认证鉴权后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考