JWT 实现 HS、RS、ES 和 ED 签名与验签
签名方式 | 算法 | 密钥类型 | 签名要点 | 验签要点 |
---|---|---|---|---|
HS | HMAC-SHA256 | 对称密钥 | - 使用 crypto/hmac 和对称密钥生成 HMAC 签名- 将 header.payload 作为数据输入 | - 使用同一密钥重新计算 HMAC 签名 - 比较计算结果与接收到的签名是否一致 |
RS | RSA-SHA256 | 公钥 + 私钥 | - 使用 crypto/rsa 生成 RSA 签名- 私钥签名,输入为 header.payload 的哈希值 | - 使用 crypto/rsa 验证 RSA 签名- 公钥解密签名后,验证是否与输入哈希值匹配 |
ES | ECDSA-P256 | 公钥 + 私钥 | - 使用 crypto/ecdsa 生成 ECDSA 签名- 签名结果为 (r, s) ,序列化并编码为 Base64URL | - 使用 crypto/ecdsa 验证签名- 解析签名为 (r, s) ,验证其与 header.payload 的哈希匹配 |
ED | Ed25519 | 公钥 + 私钥 | - 使用 crypto/ed25519 私钥直接签名完整的 header.payload 数据- 签名结果无需额外哈希处理 | - 使用 crypto/ed25519 公钥直接验证签名是否匹配完整数据 |
签名与验签实现重点
-
es算法签名和验签时算法位数必须相同:ES算法在验签时必须严格使用与签名时相同位数的算法进行验证,这一点与其他算法有所不同。(其他算法不必相同) 说明如下:
-
加密
-
验签
-
Base64URL 编码:JWT 的
Header
和Payload
都需编码。 -
数据输入:签名计算与验证的输入数据始终是
Base64URL(Header) + "." + Base64URL(Payload)
。 -
密钥管理:对称密钥 (HS) 要妥善分发,公私钥对 (RS/ES/ED) 要安全存储。
go案例
hs.go
package jwteximport ("github.com/golang-jwt/jwt/v5""log"
)type HS struct {Key stringSignMethod HSSignMethod
}type HSSignMethod stringconst (HS256 HSSignMethod = "HS256"HS384 HSSignMethod = "HS384"HS512 HSSignMethod = "HS512"
)// hs HMAC(Hash-based Message Authentication Code)用的hash-based
func (hs *HS) getSignMethod() *jwt.SigningMethodHMAC {// *jwt.SigningMethodHMAC 是 jwt.SigningMethod 接口的具体实现之一。通过返回具体的实现类型,// 可以确保你使用的是 HMAC 签名方法,而不是其他类型的签名方法(如 RSA 或 ECDSA)。switch hs.SignMethod {case HS256:return jwt.SigningMethodHS256case HS384:return jwt.SigningMethodHS384case HS512:return jwt.SigningMethodHS512default:return jwt.SigningMethodHS256}
}// Sign 签名
func (hs *HS) Sign(data jwt.Claims) (string, error) {token := jwt.NewWithClaims(hs.getSignMethod(), data)sign, err := token.SignedString([]byte(hs.Key))if err != nil {log.Println(err)return "", err}return sign, nil
}// Verify 验签,获取数据
func (hs *HS) Verify(sign string, data jwt.Claims) error {_, err := jwt.ParseWithClaims(sign, data, func(token *jwt.Token) (interface{}, error) {return []byte(hs.Key), nil})return err
}
rsa.go 私钥签名、公钥验证
package jwteximport ("github.com/golang-jwt/jwt/v5""log"
)type RS struct {SignMethod RSSignMethodPublicKey stringPrivateKey string
}type RSSignMethod stringconst (RS256 RSSignMethod = "RS256"RS384 RSSignMethod = "RS384"RS512 RSSignMethod = "RS512"
)func (rs *RS) getSignMethod() *jwt.SigningMethodRSA {switch rs.SignMethod {case RS512:return jwt.SigningMethodRS512case RS384:return jwt.SigningMethodRS384case RS256:return jwt.SigningMethodRS256default:return jwt.SigningMethodRS256}
}// Sign 签名 私钥签名、公钥验证
func (rs *RS) Sign(data jwt.Claims) (string, error) {token := jwt.NewWithClaims(rs.getSignMethod(), data)pKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(rs.PrivateKey))sign, err := token.SignedString(pKey)if err != nil {log.Println(err)return "", err}return sign, nil
}// Verify 验签,获取数据
func (rs *RS) Verify(sign string, data jwt.Claims) error {_, err := jwt.ParseWithClaims(sign, data, func(token *jwt.Token) (interface{}, error) {return jwt.ParseRSAPublicKeyFromPEM([]byte(rs.PublicKey))})return err
}
es.go 私钥签名、公钥验证
package jwteximport ("github.com/golang-jwt/jwt/v5""log"
)type ES struct {SignMethod ESSignMethodPublicKey stringPrivateKey string
}type ESSignMethod stringconst (ES256 ESSignMethod = "ES256"ES384 ESSignMethod = "ES384"ES512 ESSignMethod = "ES512"
)func (es *ES) getSignMethod() *jwt.SigningMethodECDSA {switch es.SignMethod {case ES512:return jwt.SigningMethodES512case ES384:return jwt.SigningMethodES384case ES256:return jwt.SigningMethodES256default:return jwt.SigningMethodES256}
}// Sign 签名 私钥签名、公钥验证
func (es *ES) Sign(data jwt.Claims) (string, error) {token := jwt.NewWithClaims(es.getSignMethod(), data)pKey, err := jwt.ParseECPrivateKeyFromPEM([]byte(es.PrivateKey))if err != nil {log.Println(err)return "", err}sign, err := token.SignedString(pKey)if err != nil {log.Println(err)return "", err}return sign, nil
}// Verify 验签,获取数据
func (es *ES) Verify(sign string, data jwt.Claims) error {_, err := jwt.ParseWithClaims(sign, data, func(token *jwt.Token) (interface{}, error) {return jwt.ParseECPublicKeyFromPEM([]byte(es.PublicKey))})return err
}
ed.go 私钥签名、公钥验证
package jwteximport ("github.com/golang-jwt/jwt/v5""log"
)type ED struct {PrivateKey stringPublicKey string
}// Sign 签名
func (ed *ED) Sign(data jwt.Claims) (string, error) {token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, data)pKey, err := jwt.ParseEdPrivateKeyFromPEM([]byte(ed.PrivateKey))if err != nil {log.Println(err)return "", err}sign, err := token.SignedString(pKey)if err != nil {log.Println(err)return "", err}return sign, err
}// Verify 验签,并获取数据
func (ed *ED) Verify(sign string, data jwt.Claims) error {_, err := jwt.ParseWithClaims(sign, data, func(token *jwt.Token) (interface{}, error) {return jwt.ParseEdPublicKeyFromPEM([]byte(ed.PublicKey))})return err
}
jwt.go
package jwteximport ("github.com/golang-jwt/jwt/v5"
)type Data struct {Name stringAge intGender intjwt.RegisteredClaims
}type Jwt interface {Sing(data jwt.Claims) (string, error)Verify(sign string, data jwt.Claims) error
}
https://github.com/0voice