- 网络安全
- 认证鉴权
- 后端
【免费下载链接】jose
JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes
导读
GeneralJWSInput是 jose 库中描述General JWS(通用 JSON 序列化 JWS)校验函数输入结构的核心 TypeScript 接口,它定义了多签名 JWS 文档中payload与signatures两个成员的字段类型与语义约束,并被generalVerify等校验函数直接消费。读完本文,你将掌握 General JWS 的数据结构规范、payload在 RFC 7797 未编码载荷(b64: false)与分离签名(detached signature)场景下的特殊形态、signatures数组与扁平化结构(Flattened JWS)的对应关系,以及该输入结构在 jose 源码(src/jws/general/verify.ts、src/types.d.ts)中的实际校验流程。
General JWS 与 GeneralJWSInput 的定位
在 JWS(JSON Web Signature,RFC 7515)标准中,存在三种序列化形态:Compact(紧凑)序列化、Flattened(扁平)JSON 序列化与 General(通用)JSON 序列化。其中General 序列化是最完整的 JSON 表达形式:它在一个 JSON 对象中同时承载一份载荷(payload)与一个或多个签名/MAC(MAC,Message Authentication Code,即基于共享密钥的认证码)条目,每个条目可以拥有各自独立的算法、密钥和头部参数。
GeneralJWSInput正是 jose 库为这一形态的校验(verify)输入而定义的接口。从源码注释可见其定位(src/types.d.ts):
/** * General JWS definition for verify function inputs, allows payload as {@link !Uint8Array} for * detached signature validation. */ export interface GeneralJWSInput { payload: string | Uint8Array signatures: Omit<FlattenedJWSInput, 'payload'>[] }它明确了两点关键设计:
- 该接口面向校验输入,即由签名方产出、交给校验方解析的 JWS 文档;
payload除了标准的 BASE64URL 字符串外,还允许Uint8Array,这是为分离签名(detached signature)场景预留的能力——签名内容不随文档传输时,校验方可将原始载荷以二进制形式补齐。
GeneralJWSInput 的成员结构
GeneralJWSInput仅有payload与signatures两个必填成员,其完整定义记录于接口文档 docs/types/interfaces/GeneralJWSInput.md。
payload:JWS 载荷的两种合法形态
payload的类型为string | Uint8Array,其语义按文档原文为:
The "payload" member MUST be present and contain the value BASE64URL(JWS Payload).
也就是说,按照 JWS 标准,"payload" 成员必须存在,且其值是对 JWS 原始载荷做 BASE64URL 编码后的字符串。例如:
const jws = { payload: 'SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4', signatures: [ { signature: 'FVVOXwj6kD3DqdfD9yYqfT2W9jv-Nop4kOehp_DeDGNB5dQNSPRvntBY6xH3uxlCxE8na9d_kyhYOcanpDJ0EA', protected: 'eyJhbGciOiJFUzI1NiJ9', }, ], }上述payload值是字符串It’s a dangerous business, Frodo, going out your door.的 BASE64URL 编码,这也与测试用例 test/jws/general.test.ts 中GeneralSign签名输出的 payload 值完全一致。
RFC 7797 未编码载荷(b64: false)时的例外:当 JWS 头部声明了"b64": false(即启用 RFC 7797 的 Unencoded Payload Option,将b64列入crit扩展参数)时,载荷不再做 BASE64URL 编码,"payload" 成员的含义随之改变。此时文档允许调用方直接传入Uint8Array形式的原始载荷。这一点同时落在类型层面:payload: string | Uint8Array的联合类型正是为这两种模式设计的。
signatures:签名条目数组
signatures的类型为:
signatures: Omit<FlattenedJWSInput, 'payload'>[]其语义按文档原文为:
The "signatures" member value MUST be an array of JSON objects. Each object represents a signature or MAC over the JWS Payload and the JWS Protected Header.
即signatures成员的值必须是一个 JSON 对象数组,其中每个对象代表一个对「JWS Payload + JWS Protected Header」签名或计算 MAC 的条目。TypeScript 工具类型Omit<FlattenedJWSInput, 'payload'>表示:每个条目在结构上等价于扁平化输入接口FlattenedJWSInput(见 docs/types/interfaces/FlattenedJWSInput.md)去掉payload成员后的剩余部分。
signatures 条目与 FlattenedJWSInput 的对应关系
要理解signatures条目的内部字段,必须先理解FlattenedJWSInput。一个 General JWS 本质上可以视为「多个 Flattened JWS 共享同一 payload」的组合体,因此其条目结构与扁平化结构高度一致。FlattenedJWSInput在源码中的定义(src/types.d.ts)包含四个成员:
| 成员 | 类型 | 必填性 | 语义 |
|---|---|---|---|
payload | string \| Uint8Array | 必填 | 被 Omit 移除,因为载荷上浮到 GeneralJWSInput 顶层共享 |
signature | string | 必填 | BASE64URL(JWS Signature),即该条目的签名值或 MAC 值 |
header | JWSHeaderParameters | 可选 | JWS未保护头部(Unprotected Header),以未编码的 JSON 对象呈现而非字符串,其中的头部参数值不受完整性保护 |
protected | string | 可选 | BASE64URL(UTF8(JWS Protected Header)),即受保护头部的 BASE64URL 编码,其中的参数值受完整性保护 |
因此,signatures数组中的每个元素结构如下:
{ signature: string, // 必填,BASE64URL 编码的签名/MAC protected?: string, // 可选,BASE64URL(UTF8(受保护头部)) header?: JWSHeaderParameters, // 可选,未编码的未保护头部 JSON 对象 }这里有两个值得注意的标准约束:
- protected 与 header 的缺席规则:当 JWS Protected Header 非空时
protected成员必须存在;为空时则必须缺席。未保护头部同理——非空时header必须存在,否则必须缺席; - 完整性保护差异:
protected中的头部参数被纳入签名计算,因此任何篡改都会导致签名校验失败;而header中的参数不参与签名计算,属于「告知性质」,校验方不应仅凭未保护头部做出安全决策。
从输入到校验:generalVerify 如何消费 GeneralJWSInput
GeneralJWSInput是generalVerify函数的第一个参数类型(见 docs/jws/general/verify/functions/generalVerify.md)。其签名如下:
export function generalVerify( jws: types.GeneralJWSInput, key: types.KeyInput, options?: types.VerifyOptions, ): Promise<types.GeneralVerifyResult>generalVerify的实现(src/jws/general/verify.ts)完整体现了GeneralJWSInput各成员的校验逻辑:
- 结构校验:首先要求
jws必须是对象(否则抛出JWSInvalid,错误码ERR_JWS_INVALID);随后校验signatures必须为数组且每个元素都是对象,任何一条不合规都会抛错(src/jws/general/verify.ts); - 快照与解析:对每个签名条目调用
snapshotJws做快照,解析protected头部的 BASE64URL,并依据crit中是否包含b64、b64是否为布尔值,将校验模式归类为 0/1/2 三种之一(src/jws/general/verify.ts); - RFC 7797 一致性检查:用位运算
modes |= mode统计各条目的 b64 模式,一旦同时出现 b64: true 与 b64: false 的混合使用(modes === 3),立即抛出JWSInvalid('inconsistent use of JWS Unencoded Payload (RFC7797)')(src/jws/general/verify.ts); - 逐条验签:遍历
signatures,对每条执行verifySignature,返回第一个验证成功的条目的结果;若全部失败则抛出JWSSignatureVerificationFailed(src/jws/general/verify.ts)。
需要特别指出的是:由于signatures数组共享同一个顶层payload,验证失败与格式错误的边界被刻意模糊——当payload缺失或prepareVerify解析失败时,实现会统一抛出JWSSignatureVerificationFailed而非内部错误,以避免向调用方泄露「是文档格式错误还是签名不匹配」的区分信息(src/jws/general/verify.ts)。
校验成功后的返回结构为GeneralVerifyResult(见 docs/types/interfaces/GeneralVerifyResult.md),仅包含验证成功的那一个签名条目的解码结果:
| 成员 | 类型 | 说明 |
|---|---|---|
payload | Uint8Array | 解码后的 JWS 载荷 |
protectedHeader? | JWSHeaderParameters | 该条目的受保护头部 |
unprotectedHeader? | JWSHeaderParameters | 该条目的未保护头部 |
其余签名条目的头部仅用于 RFC 7797 一致性检查,不会出现在结果中——校验方应当只信任返回值中携带的数据。
输入侧的对称产物:GeneralJWS 与 GeneralSign
与校验输入GeneralJWSInput对应的输出侧接口是GeneralJWS(见 docs/types/interfaces/GeneralJWS.md),两者结构几乎一致,差异仅在payload类型:
| 接口 | payload 类型 | 用途 |
|---|---|---|
GeneralJWSInput | string \| Uint8Array | 校验函数输入,Uint8Array用于分离签名验证 |
GeneralJWS | string | 签名函数输出;RFC 7797 b64: false 时返回空字符串 |
两者在源码中相邻定义(src/types.d.ts),GeneralJWS.signatures同样为Omit<FlattenedJWSInput, 'payload'>[]。
在签名侧,GeneralSign 类 与 Signature 接口 负责构造满足GeneralJWSInput形态的文档。其典型用法(文档内置示例):
const jws = await new jose.GeneralSign( new TextEncoder().encode('It’s a dangerous business, Frodo, going out your door.'), ) .addSignature(ecPrivateKey) .setProtectedHeader({ alg: 'ES256' }) .addSignature(rsaPrivateKey) .setProtectedHeader({ alg: 'PS256' }) .sign() console.log(jws)GeneralSign.sign()的实现(src/jws/general/sign.ts)会逐条调用createSignature生成签名,并约束:
- 至少添加一个签名(否则抛
JWSInvalid('at least one signature must be added')); - 所有条目的 b64 模式必须一致,否则抛出与校验侧相同的
inconsistent use of JWS Unencoded Payload (RFC7797); - 当 b64: false 时,输出的
GeneralJWS.payload为空字符串(因为载荷不再编码进文档,而是交由调用方通过分离签名机制传递)。
这一输出与输入结构的对称关系,被测试用例 test/jws/general.test.ts 与 test/jws/general.test.ts 直接验证:常规模式产出 BASE64URL payload,b64: false 模式产出空 payload,且两种模式的signatures.length均为 2。
实战:分离签名(detached signature)验证
GeneralJWSInput允许payload为Uint8Array的核心价值在于分离签名场景:当载荷体积大、不宜随签名文档重复传输时,发送方只传送signatures数组,接收方用自己持有的原始载荷补全payload后再校验。
import { generalVerify } from 'jose' // 假设载荷已在传输中单独送达(未做 BASE64URL 编码) const originalPayload = new TextEncoder().encode('It’s a dangerous business, Frodo, going out your door.') // 收到的 JWS 文档仅含签名条目,payload 以 Uint8Array 补齐 const jws = { payload: originalPayload, // 分离签名:直接传入原始字节 signatures: [ { signature: 'FVVOXwj6kD3DqdfD9yYqfT2W9jv-Nop4kOehp_DeDGNB5dQNSPRvntBY6xH3uxlCxE8na9d_kyhYOcanpDJ0EA', protected: 'eyJhbGciOiJFUzI1NiJ9', }, ], } const { payload, protectedHeader } = await generalVerify(jws, publicKey) console.log(protectedHeader) console.log(new TextDecoder().decode(payload))对于 RFC 7797 b64: false 的分离签名,文档同样要求传入Uint8Array载荷。校验侧的实现对此有专门处理:inputPayload instanceof Uint8Array ? new Uint8Array(inputPayload) : inputPayload,并在验签前通过encodeJsonUnencodedPayload将未编码载荷重新组织进签名输入(src/jws/general/verify.ts)。
常见校验错误与排查
基于generalVerify源码与测试(test/jws/general.test.ts),以下输入形态会被拒绝:
| 错误输入形态 | 异常类型 | 触发位置 |
|---|---|---|
jws不是对象(如 null、字符串) | JWSInvalid | src/jws/general/verify.ts |
signatures不是数组,或数组元素不是对象 | JWSInvalid | src/jws/general/verify.ts |
| 各签名条目的 b64 模式混合(true 与 false 并存) | JWSInvalid | src/jws/general/verify.ts |
| 所有签名均验证失败 / payload 缺失 | JWSSignatureVerificationFailed | src/jws/general/verify.ts |
此外,签名侧对头部还有「只能设置一次」的约束:对同一个Signature实例重复调用setProtectedHeader或setUnprotectedHeader会抛出TypeError(消息分别为setProtectedHeader can only be called once与setUnprotectedHeader can only be called once),且头部值必须是纯对象(null、字符串、数组、Date、数字、布尔值均会触发ERR_JWS_INVALID)——测试用例 test/jws/general.test.ts 逐项覆盖了这些边界。
小结
GeneralJWSInput是 jose 通用 JSON 序列化 JWS 校验链路的类型基石:payload承载 BASE64URL 编码的载荷并在 RFC 7797 / 分离签名场景下退化为Uint8Array;signatures由去除payload的FlattenedJWSInput条目构成数组,每个条目包含必填的signature与可选的protected、header。它既是被generalVerify直接消费的输入契约,又与签名侧GeneralSign产出的GeneralJWS形成对称闭环。理解该结构,是正确构造、传输与校验多签名 JWS 文档、以及实现分离签名方案的前提。
进一步阅读:接口定义 docs/types/interfaces/GeneralJWSInput.md、docs/types/interfaces/FlattenedJWSInput.md、docs/types/interfaces/GeneralJWS.md;校验实现 src/jws/general/verify.ts;签名实现 src/jws/general/sign.ts;测试用例 test/jws/general.test.ts。
- 网络安全
- 认证鉴权
- 后端
【免费下载链接】jose
JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes
相关推荐
jose 中 FlattenedJWS 接口全解析:Flattened JSON 序列化的 JWS 令牌结构与实战
jose 中 FlattenedJWS 接口全解析:Flattened JSON 序列化的 JWS 令牌结构与实战 导读 FlattenedJWS 是 jose
网络安全认证鉴权后端jose 库 GeneralSign 类完全指南:构建与签名 General JWS(通用 JSON 序列化签名对象)
jose 库 GeneralSign 类完全指南:构建与签名 General JWS(通用 JSON 序列化签名对象) 导读 GeneralSign 是 jos
网络安全认证鉴权后端jose 库 FlattenedSign 使用指南:深入解析 Flattened JSON 序列化的 JWS 签名实现
jose 库 FlattenedSign 使用指南:深入解析 Flattened JSON 序列化的 JWS 签名实现 FlattenedSign 是 jose
网络安全认证鉴权后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考