go-jose不支持直接加密jwt,而需先构建jwt.claims并序列化为字节,再用jwe.encrypt()加密生成标准jwe compact格式(a.b.c.d.e),解密时须先jwe.parseencrypted()再jwt.parse()。

go-jose 不适合直接用于“JWT 加密”——它支持 JWE(JSON Web Encryption),但 JWT 本身是语义概念,RFC 7519 明确规定 JWT 必须是 JWS 或 JWE 的序列化形式之一。你真正要做的,是用 go-jose 构建 JWE 包裹的 JWT 载荷,而不是“给 JWT 加密”。
下面直奔实操:
为什么不能直接对 jwt.Token 调用 Encrypt()?
go-jose 的 jwt 包(v4)只负责声明构造和序列化,不提供加密能力;加密必须走 jwe 流程。常见错误是试图:jwt.Sign(token, jwt.WithKey(...)) → 再拿结果去加密 → 失败,因为 JWS compact 格式(xxx.yyy.zzz)不是合法 JWE 输入。
正确路径是:先构造 jwt.Claims,再用 jwe.Encrypt() 将其作为明文载荷加密,**而非对已签名的 JWT 字符串加密**。
- JWS 是签名(防篡改),JWE 是加密(防泄露),二者目的不同,不能叠加在同一 token 上(除非嵌套,但
go-josev4 不支持 JWE-within-JWS) -
jwt.Parse()只能解析 JWS 或 JWE compact 格式,不能解析纯 JSON —— 所以你加密后的输出必须是标准 JWE compact(a.b.c.d.e)才能被下游正确解密
如何用 go-jose/v4 构造可解密的 JWT 载荷(JWE)?
核心步骤:用 jwt.Claims 构建结构体 → 序列化为 []byte → 传给 jwe.Encrypt() → 拿 compact 字符串。
示例关键代码:
// 1. 构建 claims(注意:不要调用 jwt.Sign!)
cl := jwt.Claims{
Subject: "user-123",
Issuer: "auth-service",
Expiry: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
}
raw, err := json.Marshal(cl) // ← 必须手动序列化
if err != nil {
return "", err
}
<p>// 2. 准备密钥(这里用 AES 密钥包装,需 256-bit key)
key := make([]byte, 32)
rand.Read(key)</p><p>// 3. 加密(使用 A256GCM + A256KW 组合)
jweObj, err := jwe.Encrypt(raw,
jwe.WithKey(jwa.A256KW, key),
jwe.WithContentEncryption(jwa.A256GCM),
)
if err != nil {
return "", err
}</p><p>// 4. 输出 compact JWE(形如 a.b.c.d.e)
compact, err := jweObj.CompactSerialize()
</p>
-
jwe.Encrypt()第一个参数必须是原始字节([]byte),不是jwt.Token实例 -
jwa.A256KW和jwa.A256GCM必须匹配密钥长度(32 字节);用错会 panic 或静默失败 - 若要用 RSA 公钥加密(比如发给特定接收方),替换
jwe.WithKey(jwa.RSA_OAEP, pubKey),私钥解密时用jwe.Decrypt()+privKey
解密时为何 jwt.Parse() 会失败?
常见现象:拿到 JWE compact 字符串后,直接丢给 jwt.Parse() → 报错 invalid token format 或 failed to parse header。
原因:JWE compact 是五段式(eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2R0NNIn0....),而 jwt.Parse() 只认三段式 JWS 或 JWE JSON 序列化格式(非 compact)。你必须先用 jwe.ParseEncrypted() 解密出原始 JSON,再交给 jwt.Parse()。
正确流程:
// 1. 解析 JWE compact
obj, err := jwe.ParseEncrypted(compact)
if err != nil {
return nil, err
}
<p>// 2. 解密(需提供对应私钥或对称密钥)
decrypted, err := obj.Decrypt(key) // ← 这里 key 是加密时用的 same key
if err != nil {
return nil, err
}</p><p>// 3. 现在 decrypted 是 []byte,可安全 jwt.Parse()
token, err := jwt.Parse(decrypted, jwt.WithValidate(true))
</p>
- 漏掉
jwe.ParseEncrypted()直接喂给jwt.Parse()是最常踩的坑 -
obj.Decrypt()返回的是原始 JSON 字节,不是jwt.Token;jwt.Parse()才负责反序列化成结构体 - 如果加密用了 RSA-OAEP,解密时必须传
*rsa.PrivateKey,且密钥格式必须是 PKCS#8(不是 PKCS#1)
对称密钥 vs 非对称密钥:选哪个?
生产环境必须根据场景选,不是“越复杂越好”:
-
服务间通信(如微服务 API):优先用
jwa.A256KW + jwa.A256GCM,密钥通过 KMS 或 Vault 分发,性能高、无证书管理开销 - 客户端→服务端(如前端提交敏感表单):必须用 RSA-OAEP 或 ECDH-ES,公钥可公开,私钥绝不离开服务端
-
别用 HS256 做 JWE 密钥:
jwa.HS256是签名算法,不能用于jwe.WithKey();JWE 密钥封装算法必须是xxxKW或xxxGCMKW系列
JWE 的密钥生命周期比 JWS 更敏感——一旦对称密钥泄露,所有历史加密 token 都可解密。非对称方案下,私钥泄露仅影响未来 token,旧 token 因无私钥无法解密(前提是没存明文备份)。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











