4. JWT Header详解
JWT(JSON Web Token)的 Header(头部)是令牌的第一部分,通常是一个 Base64URL 编码的 JSON 对象。它承载了令牌的元数据,包括签名算法、令牌类型等关键信息。服务端在验证 JWT 时,首先解析 Header 以确定使用何种算法进行签名校验。理解 Header 的结构与字段,是正确实现 JWT 鉴权的基础。
4.1 Header 标准字段
JWT 规范(RFC 7519)定义了两个标准字段,所有 JWT 实现都应支持:
| 字段名 | 全称 | 必选 | 说明 |
|---|---|---|---|
alg |
Algorithm | 是 | 签名或加密算法名称,如 HS256、RS256、none |
typ |
Type | 否(推荐) | 令牌媒体类型,固定为 JWT |
最佳实践: 即使
typ 字段非强制,也建议始终包含 "typ": "JWT"。这有助于中间件或网关在混合令牌场景中快速识别 JWT,避免与其他令牌格式(如 SAML、Paseto)混淆。
4.1.1 alg 字段详解
alg 是 Header 中唯一必须存在的字段。它告诉验证方该 JWT 使用了哪种签名或加密算法。常见的 alg 取值包括:
- HS256:HMAC with SHA-256,对称签名算法。
- HS384:HMAC with SHA-384。
- HS512:HMAC with SHA-512。
- RS256:RSA with SHA-256,非对称签名算法。
- RS384:RSA with SHA-384。
- RS512:RSA with SHA-512。
- ES256:ECDSA with P-256 and SHA-256。
- none:不签名(仅用于调试,生产环境禁止使用)。
4.1.2 typ 字段详解
typ 字段用于声明令牌的媒体类型。根据 RFC 7519,其值应设置为 "JWT"。在某些复合结构中(如 JWE 嵌套 JWS),typ 可以帮助解析器区分内部令牌类型。一个典型的 Header 示例:
{
"alg": "HS256",
"typ": "JWT"
}
4.2 常见签名算法:HS256 与 RS256
在 Web 接口安全中,HS256 和 RS256 是最广泛使用的两种签名算法。它们分别代表对称与非对称签名体系,在密钥管理、性能、安全性方面有显著差异。
| 对比维度 | HS256(HMAC-SHA256) | RS256(RSA-SHA256) |
|---|---|---|
| 密钥类型 | 对称密钥(同一个密钥签名和验证) | 非对称密钥(私钥签名,公钥验证) |
| 密钥分发 | 需安全共享密钥给所有验证方 | 公钥可公开分发,私钥仅签发方持有 |
| 性能 | 签名和验证都很快(计算量小) | 签名较慢,验证较快(但比 HS256 慢) |
| 适用场景 | 单一服务内部、微服务间信任网络 | 第三方分发、开放 API、多服务独立验证 |
| 安全风险 | 密钥泄露后所有方均可伪造令牌 | 私钥泄露风险仅影响签发方 |
| 典型 Header | "alg":"HS256" |
"alg":"RS256" |
安全警告: 使用 HS256 时,务必确保密钥强度足够(至少 256 位随机字节),并且通过安全的渠道(如 Vault、KMS)分发给所有验证服务。切勿将密钥硬编码在代码仓库中。对于多服务架构,RS256 通常是更安全的选择,因为公钥可以安全地通过公开端点分发。
4.3 自定义 Header
除了标准字段,JWT Header 允许添加自定义字段,用于携带与令牌处理相关的元数据。自定义字段通常以 非冲突命名 为原则,避免与标准字段或未来扩展冲突。常见做法是使用前缀或特定命名空间。
4.3.1 常见自定义字段示例
| 自定义字段 | 含义 | 示例值 |
|---|---|---|
kid |
Key ID,用于标识使用哪把密钥签名 | "kid": "rsa-key-v2" |
cty |
Content Type,嵌套 JWT 时声明内部内容类型 | "cty": "JWT" |
crit |
Critical,声明哪些 Header 字段必须被理解 | "crit": ["exp", "kid"] |
jku |
JWK Set URL,指向公钥集合的 URL | "jku": "https://auth.example.com/.well-known/jwks.json" |
x5c |
X.509 Certificate Chain,嵌入证书链 | "x5c": ["MIIE..."] |
4.3.2 自定义 Header 的 Go 实现示例
在 Go 中使用 github.com/golang-jwt/jwt/v5 库时,可以通过自定义结构体扩展 Header:
package main
import (
"fmt"
"github.com/golang-jwt/jwt/v5"
)
// 自定义 Claims 结构体
type CustomClaims struct {
UserID string `json:"user_id"`
Role string `json:"role"`
jwt.RegisteredClaims
}
// 自定义 Header 字段
type CustomHeader struct {
jwt.Header
KeyID string `json:"kid,omitempty"`
AppID string `json:"app_id,omitempty"`
}
func main() {
// 创建自定义 Header
header := CustomHeader{
Header: jwt.Header{
Algorithm: "RS256",
Type: "JWT",
},
KeyID: "my-rsa-key-2024",
AppID: "gateway-v3",
}
// 创建 Claims
claims := CustomClaims{
UserID: "u-10086",
Role: "admin",
RegisteredClaims: jwt.RegisteredClaims{
Issuer: "auth-service",
},
}
// 使用自定义 Header 和 Claims 生成 Token
token := jwt.NewWithClaims(header, claims)
tokenString, err := token.SignedString(privateKey)
if err != nil {
panic(err)
}
fmt.Println(tokenString)
}
关键点: 自定义 Header 字段必须被验证方理解,否则应使用
crit 字段声明。如果验证方遇到 crit 中列出的字段但不支持,必须拒绝该令牌。这防止了因忽略关键 Header 字段而导致的安全漏洞。
4.4 JWT Header 结构总览
下图展示了 JWT Header 的标准字段、常见算法选择以及自定义扩展的整体结构:
4.5 算法选择决策流程
在实际项目中,选择 HS256 还是 RS256 需要根据架构和安全需求权衡。以下决策流程可以帮助你做出选择:
4.6 安全最佳实践
- 算法白名单验证: 在验证 JWT 时,必须检查
alg字段是否在服务端允许的算法列表中。防止攻击者将alg改为none或弱算法绕过签名验证。 - 避免算法混淆攻击: 如果服务端支持 RS256,但攻击者将
alg改为HS256并使用公钥作为 HMAC 密钥签名,可能导致验证通过。解决方案:始终根据alg字段选择对应的验证方法,不要混用。 - 使用 kid 支持密钥轮换: 在 Header 中包含
kid字段,服务端根据kid查找对应的密钥进行验证。这允许在不中断服务的情况下轮换密钥。 - 最小化自定义字段: 只在 Header 中放置与令牌处理直接相关的元数据。业务数据应放在 Payload 中,而非 Header。
Go 实现提示: 使用
jwt.NewWithClaims() 时,第一个参数可以是实现了 jwt.SigningMethod 接口的对象,也可以是包含 Algorithm 字段的结构体。推荐始终显式指定算法,避免依赖默认值。在验证端,使用 jwt.ParseWithClaims() 并传入自定义的 KeyFunc,在 KeyFunc 中检查 token.Header["alg"] 是否在允许列表中。