3. JWT Header 详解:typ、alg 字段与签名算法选择
JWT Header(头部)是 JWT 三段式结构中的第一段,承载着令牌的元数据。它虽然只有几个字段,却决定了整个令牌的签名算法、类型声明以及后续的解析方式。本章将深入解析 typ 和 alg 字段的语义,并对比三种主流签名算法 HS256、RS256、ES256 的选型策略。
3.1 Header 结构概览
一个典型的 JWT Header 是一个 JSON 对象,经过 Base64URL 编码后成为令牌的第一部分。其核心字段如下:
| 字段 | 必选 | 取值示例 | 说明 |
|---|---|---|---|
alg |
是 | "HS256" |
签名或加密算法,JWT 标准中唯一强制字段 |
typ |
否 | "JWT" |
令牌类型声明,通常固定为 "JWT" |
cty |
否 | "JWT" |
内容类型,用于嵌套签名或加密场景 |
kid |
否 | "key-2024-01" |
密钥 ID,用于多密钥轮换场景 |
实践要点:即使
typ 字段非强制,主流 JWT 库(如 jose、PyJWT、jsonwebtoken)在解析时通常不会校验该字段。但建议显式声明 "typ": "JWT",以增强令牌的自描述性,避免与其他令牌格式(如 JWE、JWS)混淆。
3.2 typ 字段:令牌类型声明
typ(Type)字段用于声明令牌的媒体类型。在 JWT 规范(RFC 7519)中,推荐使用 "JWT" 作为取值。其核心作用包括:
- 格式区分:在混合使用 JWS(JSON Web Signature)和 JWE(JSON Web Encryption)的系统里,
typ帮助解析器快速识别令牌类型。 - 嵌套令牌:当 JWT 被用作另一个 JWT 的 Payload 时(嵌套签名),外层 Header 的
cty字段设为"JWT",内层 Header 的typ保持"JWT"。 - 扩展性:非标准场景下可自定义取值,但需确保接收方能够理解。
安全警告:不要依赖
typ 字段做安全决策。攻击者可以轻易修改该字段值。令牌类型校验应基于 alg 字段和密钥匹配,而非 typ。
3.3 alg 字段:签名算法核心
alg(Algorithm)字段是 JWT Header 中唯一强制字段,它指定了用于签名或完整性保护的算法。算法取值遵循 JWA(JSON Web Algorithm,RFC 7518)规范。根据算法类型,可分为三大类:
| 算法族 | 算法名称 | 类型 | 密钥 | 典型场景 |
|---|---|---|---|---|
| HMAC | HS256 / HS384 / HS512 | 对称 | 单一共享密钥 | 内部微服务通信 |
| RSA | RS256 / RS384 / RS512 | 非对称 | 私钥签名,公钥验签 | 第三方授权、跨域认证 |
| ECDSA | ES256 / ES384 / ES512 | 非对称 | 私钥签名,公钥验签 | 移动端、IoT、高性能场景 |
3.4 三种主流签名算法深度对比
在实际工程中,HS256、RS256、ES256 是最常见的三种选择。下面从多个维度进行对比:
| 对比维度 | HS256 (HMAC-SHA256) | RS256 (RSA-SHA256) | ES256 (ECDSA-P256) |
|---|---|---|---|
| 密钥类型 | 对称密钥(同一密钥签名和验签) | 非对称密钥对(私钥签名,公钥验签) | 非对称密钥对(私钥签名,公钥验签) |
| 签名长度 | 32 字节(固定) | 256 字节(2048 位密钥时) | 64 字节(固定) |
| 计算性能 | 极快(对称运算) | 慢(大数模幂运算) | 快(椭圆曲线运算) |
| 密钥分发 | 困难(需安全通道共享密钥) | 容易(公钥可公开分发) | 容易(公钥可公开分发) |
| 密钥轮换 | 复杂(所有依赖方需同步更新) | 简单(仅更新签名方私钥,验签方公钥不变) | 简单(仅更新签名方私钥,验签方公钥不变) |
| 安全强度 | 高(取决于密钥熵值) | 高(2048 位以上密钥) | 高(等效 3072 位 RSA) |
| 令牌体积 | 小 | 大(签名部分占 256 字节) | 小(签名部分仅 64 字节) |
| 典型场景 | 内部系统、网关认证 | 开放 API、第三方登录 | 移动端、IoT、高并发 |
选型建议:
- HS256:适用于完全可控的内部网络,所有服务间共享同一个密钥。注意密钥必须足够复杂(至少 256 位随机数),且定期轮换。
- RS256:适用于需要将公钥暴露给第三方的场景,如 OAuth 2.0 授权服务器。签名方和验签方解耦,公钥可通过 JWKS 端点安全分发。
- ES256:适用于对令牌体积和性能有严格要求的场景,如移动 App 或 IoT 设备。ECDSA 在相同安全强度下密钥更短、签名更小、计算更快。
3.5 签名算法选择决策流程
下图展示了在实际架构中如何根据系统边界和信任模型选择签名算法:
3.6 算法选择实战清单
在具体项目中,可按照以下清单逐项评估:
- 信任边界:签名方和验签方是否属于同一个安全域?是 → 考虑 HS256;否 → 必须使用非对称算法。
- 密钥分发成本:能否安全地将共享密钥传递给所有验签方?困难 → 选择 RS256 或 ES256。
- 令牌体积约束:令牌是否需要在 HTTP Header 或 URL 参数中传输?体积敏感 → 优先 ES256(签名 64 字节)或 HS256(签名 32 字节)。
- 计算性能:验签操作的 QPS 预期是多少?高并发(>10K QPS)→ ES256 优于 RS256。
- 密钥轮换策略:是否需要频繁轮换密钥?是 → 非对称算法更易管理,配合
kid字段实现无缝轮换。 - 生态兼容性:客户端或第三方库是否广泛支持所选算法?HS256 和 RS256 支持最广,ES256 在较新库中已全面支持。
常见陷阱:
- 算法混淆攻击:攻击者将
alg从RS256改为HS256,并用公钥作为 HMAC 密钥验签。防范措施:服务端必须白名单校验alg字段,拒绝未预期的算法。 - 密钥硬编码:将 HS256 密钥或 RSA 私钥直接写在代码中。应使用密钥管理服务(KMS)或环境变量注入。
- 忽略 kid 校验:使用
kid字段时,必须校验该 ID 对应的密钥是否属于信任列表,防止密钥注入攻击。
3.7 Header 示例与编码
以下展示三种算法的 Header 示例及其 Base64URL 编码结果:
| 算法 | Header JSON | Base64URL 编码 |
|---|---|---|
| HS256 | {"alg":"HS256","typ":"JWT"} |
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 |
| RS256 | {"alg":"RS256","typ":"JWT","kid":"rsa-key-1"} |
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InJzYS1rZXktMSJ9 |
| ES256 | {"alg":"ES256","typ":"JWT"} |
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9 |
生成 Header 的代码示例(Node.js):
const header = {
alg: 'RS256',
typ: 'JWT',
kid: 'key-2024-01'
};
// 编码为 Base64URL
const base64UrlHeader = Buffer
.from(JSON.stringify(header))
.toString('base64')
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
console.log(base64UrlHeader);
// 输出: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleS0yMDI0LTAxIn0