如何在 JWT 中嵌入并提取 RSA 公钥(使用 jwt-go 实现)

发布时间 - 2026-01-31 00:00:00    点击率:

本文详解如何安全地将 rsa 公钥以 pem 字符串形式嵌入 jwt 的 claims 中,并在解析时动态还原为 *rsa.publickey,实现基于公钥分发的灵活验签逻辑。适用于多租户、密钥轮换或去中心化签名验证场景。

在标准 JWT 实践中,签名验证依赖服务端预置的公钥(如 jwt.Parse(..., func() { return publicKey })),但该方式缺乏灵活性:无法支持动态密钥切换、多租户独立密钥,或客户端自主选择验证方。一种常见误解是直接将 *big.Int 类型的 N/E 字段存入 claims——这不仅会因 JSON 序列化导致精度丢失(big.Int 被截断为 int64),更严重的是破坏了 JWT 的信任模型:若公钥本身可被任意篡改并嵌入 token,则签名完全失去防伪意义。

✅ 正确做法是:*将公钥序列化为标准 PEM 格式字符串(Base64 编码的 ASN.1 DER 结构),存入 claims;解析时再从该字符串反序列化为 `rsa.PublicKey**。PEM 是文本安全、标准兼容且无精度损失的表示方式,且jwt-go提供了开箱即用的jwt.ParseRSAPublicKeyFromPEM()` 辅助函数。

以下是完整可运行示例(基于 github.com/dgrijalva/jwt-go v3.x):

package main

import (
    "crypto/rand"
    "crypto/rsa"
    "crypto/x509"
    "encoding/pem"
    "fmt"
    "log"

    jwt "github.com/dgrijalva/jwt-go"
)

func main() {
    // 1. 生成 RSA 密钥对(生产环境请使用 ≥2048 位)
    privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
    if err != nil {
        log.Fatal("生成私钥失败:", err)
    }

    // 2. 构建 token 并嵌入 PEM 格式公钥
    token := jwt.New(jwt.SigningMethodRS256)
    token.Claims = jwt.MapClaims{
        "username": "victorsamuelmd",
        "exp":      time.Now().Add(time.Hour).Unix(),
        // ✅ 关键:将公钥 Marshal 为 PKIX DER,再编码为 PEM 字符串
        "public_key_pem": pemEncodePublicKey(&privateKey.PublicKey),
    }

    tokenString, err := token.SignedString(privateKey)
    if err != nil {
        log.Fatal("签名 token 失败:", err)
    }
    fmt.Println("已生成 token:", tokenString)

    // 3. 解析 token 并动态提取公钥验签
    parsedToken, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {
        // 验证签名算法是否符合预期
        if _, ok := t.Method.(*jwt.SigningMethodRSA); !ok {
            return nil, fmt.Errorf("意外的签名方法: %v", t.Header["alg"])
        }

        // 从 claims 中读取 PEM 字符串并解析为 *rsa.PublicKey
        claims, ok := t.Claims.(jwt.MapClaims)
        if !ok {
            return nil, fmt.Errorf("claims 类型断言失败")
        }
        pemStr, ok := claims["public_key_pem"].(string)
        if !ok {
            return nil, fmt.Errorf("claims 中未找到 public_key_pem 字段或类型错误")
        }

        return jwt.ParseRSAPublicKeyFromPEM([]byte(pemStr))
    })

    if err != nil {
        log.Fatal("解析/验签失败:", err)
    }
    if !parsedToken.Valid {
        log.Fatal("token 验证失败:签名无效或已过期")
    }

    fmt.Println("✅ token 验证成功!Payload:", parsedToken.Claims)
}

// pemEncodePublicKey 将 *rsa.PublicKey 转为 PEM 格式字符串
f

unc pemEncodePublicKey(pub *rsa.PublicKey) string { bytes, err := x509.MarshalPKIXPublicKey(pub) if err != nil { panic("公钥序列化失败: " + err.Error()) } pemBlock := &pem.Block{ Type: "RSA PUBLIC KEY", Bytes: bytes, } return string(pem.EncodeToMemory(pemBlock)) }

⚠️ 重要注意事项

  • 安全性警示:此方案仅适用于可信上下文(如内部微服务通信、受控 API 网关)。若 token 可能被恶意第三方截获并重放,嵌入公钥会削弱“唯一可信签发者”模型——攻击者可伪造含自己公钥的 token。此时应坚持传统方式:服务端维护权威公钥池,通过 kid(Key ID)字段索引。
  • 性能考量:每次解析都需 PEM 解析和 ASN.1 解码,比直接使用内存公钥稍慢。高并发场景建议缓存已解析的公钥(以 PEM 字符串为 key)。
  • 密钥长度:示例使用 2048 位;生产环境推荐 3072 或 4096 位以满足长期安全要求。
  • 库迁移提示:dgrijalva/jwt-go 已归档,新项目推荐迁移到 golang-jwt/jwt/v5,其 API 更清晰(如 jwt.WithValidMethods, jwt.WithValidator),且原生支持 func(token *Token) (any, error) 形式的 KeyFunc。

总结:通过 PEM 字符串嵌入公钥是一种实用的动态验签技术,它平衡了灵活性与标准兼容性。关键在于理解其适用边界——它不是替代传统公钥管理的银弹,而是特定架构下的有力补充。


# js  # git  # json  # go  # github  # golang  # 编码  # ai  # unix  # crypto  # 架构  # Error  # Token  # 字符串  # int  # 并发  # 公钥  # 适用于  # 服务端  # 的是  # 序列化  # 是一种  # 并在  # 请使用  # 第三方  # 时应 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: *服务器网站为何频现安全漏洞?  如何在云虚拟主机上快速搭建个人网站?  Laravel如何优雅地处理服务层_在Laravel中使用Service层和Repository层  JS去除重复并统计数量的实现方法  Laravel怎么生成二维码图片_Laravel集成Simple-QrCode扩展包与参数设置【实战】  Laravel如何实现多表关联模型定义_Laravel多对多关系及中间表数据存取【方法】  Laravel如何实现本地化和多语言支持?(i18n教程)  如何破解联通资金短缺导致的基站建设难题?  Laravel如何配置和使用队列处理异步任务_Laravel队列驱动与任务分发实例  Laravel如何生成PDF或Excel文件_Laravel文档导出工具与使用教程  Laravel项目结构怎么组织_大型Laravel应用的最佳目录结构实践  Laravel中DTO是什么概念_在Laravel项目中使用数据传输对象(DTO)  历史网站制作软件,华为如何找回被删除的网站?  javascript读取文本节点方法小结  北京网站制作费用多少,建立一个公司网站的费用.有哪些部分,分别要多少钱?  如何快速搭建高效WAP手机网站吸引移动用户?  php打包exe后无法访问网络共享_共享权限设置方法【教程】  Android okhttputils现在进度显示实例代码  香港服务器WordPress建站指南:SEO优化与高效部署策略  Laravel如何实现邮件验证激活账户_Laravel内置MustVerifyEmail接口配置【步骤】  ChatGPT怎么生成Excel公式_ChatGPT公式生成方法【指南】  html5的keygen标签为什么废弃_替代方案说明【解答】  Laravel API资源类怎么用_Laravel API Resource数据转换  Laravel PHP版本要求一览_Laravel各版本环境要求对照  JavaScript中如何操作剪贴板_ClipboardAPI怎么用  bootstrap日历插件datetimepicker使用方法  胶州企业网站制作公司,青岛石头网络科技有限公司怎么样?  如何在景安服务器上快速搭建个人网站?  详解MySQL数据库的安装与密码配置  原生JS获取元素集合的子元素宽度实例  如何在阿里云部署织梦网站?  Laravel辅助函数有哪些_Laravel Helpers常用助手函数大全  HTML 中如何正确使用模板变量为元素的 name 属性赋值  Laravel如何处理和验证JSON类型的数据库字段  阿里云网站搭建费用解析:服务器价格与建站成本优化指南  香港服务器租用费用高吗?如何避免常见误区?  Windows11怎样设置电源计划_Windows11电源计划调整攻略【指南】  Laravel如何实现图片防盗链功能_Laravel中间件验证Referer来源请求【方案】  Laravel怎么实现验证码功能_Laravel集成验证码库防止机器人注册  Laravel如何集成第三方登录_Laravel Socialite实现微信QQ微博登录  Win10如何卸载预装Edge扩展_Win10卸载Edge扩展教程【方法】  Laravel怎么在Controller之外的地方验证数据  如何确认建站备案号应放置的具体位置?  制作电商网页,电商供应链怎么做?  如何基于云服务器快速搭建个人网站?  iOS发送验证码倒计时应用  ai格式如何转html_将AI设计稿转换为HTML页面流程【页面】  Python企业级消息系统教程_KafkaRabbitMQ高并发应用  如何在景安云服务器上绑定域名并配置虚拟主机?  使用Dockerfile构建java web环境