golang实现JOSE标准(JWA/JWE/JWK/JWS/JWT)的安全插件库jwx的使用

Golang实现JOSE标准(JWA/JWE/JWK/JWS/JWT)的安全插件库jwx的使用

概述

github.com/lestrrat-go/jwx/v3 是一个实现了各种JWx技术(JWA/JWE/JWK/JWS/JWT,也称为JOSE)的Go模块。

特性

  • 完整覆盖JWA/JWE/JWK/JWS/JWT,而不仅仅是JWT+最小工具集
    • 支持具有多个签名的JWS消息,包括紧凑和JSON序列化
    • 支持具有分离负载的JWS
    • 支持具有未编码负载的JWS(RFC7797)
    • 支持具有多个接收者的JWE消息,包括紧凑和JSON序列化
    • 大多数操作适用于JWK或原始密钥(如*rsa.PrivateKey, *ecdsa.PrivateKey等)
  • 统一且对称的API设计
    • jws.Parse/Verify/Sign
    • jwe.Parse/Encrypt/Decrypt
    • 参数组织为显式必需参数和可选的WithXXXX()样式选项
  • 额外实用工具
    • jwk.Cache 保持JWKS始终最新
    • 支持bazel构建

示例代码

以下是一个完整示例,展示了如何使用jwx库处理JWK、JWT、JWE和JWS:

package main

import (
	"bytes"
	"fmt"
	"net/http"
	"time"

	"github.com/lestrrat-go/jwx/v3/jwa"
	"github.com/lestrrat-go/jwx/v3/jwe"
	"github.com/lestrrat-go/jwx/v3/jwk"
	"github.com/lestrrat-go/jwx/v3/jws"
	"github.com/lestrrat-go/jwx/v3/jwt"
)

func main() {
	// 示例JSON RSA私钥
	jsonRSAPrivateKey := []byte(`{
		"kty": "RSA",
		"n": "...",
		"e": "AQAB",
		"d": "...",
		"p": "...",
		"q": "...",
		"dp": "...",
		"dq": "...",
		"qi": "..."
	}`)

	// 示例负载
	payloadLoremIpsum := []byte("Lorem ipsum dolor sit amet")

	// 1. 解析JWK
	privkey, err := jwk.ParseKey(jsonRSAPrivateKey)
	if err != nil {
		fmt.Printf("failed to parse JWK: %s\n", err)
		return
	}

	// 2. 获取公钥
	pubkey, err := jwk.PublicKeyOf(privkey)
	if err != nil {
		fmt.Printf("failed to get public key: %s\n", err)
		return
	}

	// 3. 处理JWT
	{
		// 构建JWT
		tok, err := jwt.NewBuilder().
			Issuer(`github.com/lestrrat-go/jwx`).
			IssuedAt(time.Now()).
			Build()
		if err != nil {
			fmt.Printf("failed to build token: %s\n", err)
			return
		}

		// 签名JWT
		signed, err := jwt.Sign(tok, jwt.WithKey(jwa.RS256(), privkey))
		if err != nil {
			fmt.Printf("failed to sign token: %s\n", err)
			return
		}

		// 验证JWT
		{
			verifiedToken, err := jwt.Parse(signed, jwt.WithKey(jwa.RS256(), pubkey))
			if err != nil {
				fmt.Printf("failed to verify JWS: %s\n", err)
				return
			}
			_ = verifiedToken
		}

		// 处理HTTP请求
		{
			req, err := http.NewRequest(http.MethodGet, `https://github.com/lestrrat-go/jwx`, nil)
			req.Header.Set(`Authorization`, fmt.Sprintf(`Bearer %s`, signed))

			verifiedToken, err := jwt.ParseRequest(req, jwt.WithKey(jwa.RS256(), pubkey))
			if err != nil {
				fmt.Printf("failed to verify token from HTTP request: %s\n", err)
				return
			}
			_ = verifiedToken
		}
	}

	// 4. 使用JWE加密和解密任意负载
	{
		encrypted, err := jwe.Encrypt(payloadLoremIpsum, jwe.WithKey(jwa.RSA_OAEP(), pubkey))
		if err != nil {
			fmt.Printf("failed to encrypt payload: %s\n", err)
			return
		}

		decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP(), privkey))
		if err != nil {
			fmt.Printf("failed to decrypt payload: %s\n", err)
			return
		}

		if !bytes.Equal(decrypted, payloadLoremIpsum) {
			fmt.Printf("verified payload did not match\n")
			return
		}
	}

	// 5. 使用JWS签名和验证任意负载
	{
		signed, err := jws.Sign(payloadLoremIpsum, jws.WithKey(jwa.RS256(), privkey))
		if err != nil {
			fmt.Printf("failed to sign payload: %s\n", err)
			return
		}

		verified, err := jws.Verify(signed, jws.WithKey(jwa.RS256(), pubkey))
		if err != nil {
			fmt.Printf("failed to verify payload: %s\n", err)
			return
		}

		if !bytes.Equal(verified, payloadLoremIpsum) {
			fmt.Printf("verified payload did not match\n")
			return
		}
	}
}

包说明

包名 说明
jwt RFC 7519
jwk RFC 7517 + RFC 7638
jwa RFC 7518
jws RFC 7515 + RFC 7797
jwe RFC 7516

为什么选择这个库

与其他主要处理JWT的项目不同,这个模块处理JWS、JWE、JWK和JWT的完整范围。如果你不仅需要解析JWT,还需要控制JWK,或者处理不是JWT的负载,你应该考虑使用这个模块。

从实现角度来看,这个模块与其他模块的不同之处在于它非常努力地只暴露API,而不暴露内部数据。这种结构允许我们在结构体中放入额外的智能,例如在你想解析/写入自定义字段时做正确的事情。

贡献

对于错误报告和功能请求,请尽量遵循问题模板。对于错误报告或功能请求,失败的测试更好。

对于拉取请求,请确保包含测试以验证你所做的更改。


更多关于golang实现JOSE标准(JWA/JWE/JWK/JWS/JWT)的安全插件库jwx的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang实现JOSE标准(JWA/JWE/JWK/JWS/JWT)的安全插件库jwx的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


使用jwx库实现JOSE标准(JWA/JWE/JWK/JWS/JWT)的安全功能

jwx是一个功能强大的Golang库,用于实现JOSE标准(JWA/JWE/JWK/JWS/JWT)的安全功能。下面我将介绍如何使用这个库来实现常见的加密、签名和令牌功能。

安装

首先安装jwx库:

go get github.com/lestrrat-go/jwx/v2

JWT (JSON Web Token) 示例

创建JWT

package main

import (
	"fmt"
	"time"
	
	"github.com/lestrrat-go/jwx/v2/jwa"
	"github.com/lestrrat-go/jwx/v2/jwt"
)

func main() {
	// 创建JWT令牌
	token, err := jwt.NewBuilder().
		Issuer("my-issuer").
		Subject("user123").
		Audience([]string{"client1", "client2"}).
		IssuedAt(time.Now()).
		Expiration(time.Now().Add(time.Hour)).
		Claim("custom", "value").
		Build()
	if err != nil {
		fmt.Printf("failed to build token: %s\n", err)
		return
	}

	// 使用HMAC算法签名
	signed, err := jwt.Sign(token, jwt.WithKey(jwa.HS256, []byte("your-secret-key")))
	if err != nil {
		fmt.Printf("failed to sign token: %s\n", err)
		return
	}

	fmt.Printf("Signed JWT: %s\n", signed)
}

验证JWT

func verifyJWT(tokenStr string) {
	// 解析并验证JWT
	token, err := jwt.Parse([]byte(tokenStr), 
		jwt.WithVerify(true),
		jwt.WithKey(jwa.HS256, []byte("your-secret-key")),
	)
	if err != nil {
		fmt.Printf("failed to parse token: %s\n", err)
		return
	}

	// 获取声明
	fmt.Printf("Issuer: %s\n", token.Issuer())
	fmt.Printf("Subject: %s\n", token.Subject())
	if custom, ok := token.Get("custom"); ok {
		fmt.Printf("Custom claim: %v\n", custom)
	}
}

JWS (JSON Web Signature) 示例

创建JWS

package main

import (
	"fmt"
	
	"github.com/lestrrat-go/jwx/v2/jwa"
	"github.com/lestrrat-go/jwx/v2/jws"
)

func main() {
	payload := []byte("important message to sign")

	// 使用RSA私钥签名
	privateKey := `-----BEGIN PRIVATE KEY-----
... your private key here ...
-----END PRIVATE KEY-----`

	signed, err := jws.Sign(payload, jws.WithKey(jwa.RS256, []byte(privateKey)))
	if err != nil {
		fmt.Printf("failed to sign payload: %s\n", err)
		return
	}

	fmt.Printf("Signed JWS: %s\n", signed)
}

验证JWS

func verifyJWS(jwsStr string) {
	publicKey := `-----BEGIN PUBLIC KEY-----
... your public key here ...
-----END PUBLIC KEY-----`

	verified, err := jws.Verify([]byte(jwsStr), jws.WithKey(jwa.RS256, []byte(publicKey)))
	if err != nil {
		fmt.Printf("failed to verify JWS: %s\n", err)
		return
	}

	fmt.Printf("Verified payload: %s\n", verified)
}

JWE (JSON Web Encryption) 示例

加密数据

package main

import (
	"fmt"
	
	"github.com/lestrrat-go/jwx/v2/jwa"
	"github.com/lestrrat-go/jwx/v2/jwe"
)

func main() {
	payload := []byte("sensitive data to encrypt")

	// 使用RSA公钥加密
	publicKey := `-----BEGIN PUBLIC KEY-----
... your public key here ...
-----END PUBLIC KEY-----`

	encrypted, err := jwe.Encrypt(
		payload,
		jwe.WithKey(jwa.RSA_OAEP, []byte(publicKey)),
		jwe.WithContentEncryption(jwa.A256GCM),
	)
	if err != nil {
		fmt.Printf("failed to encrypt payload: %s\n", err)
		return
	}

	fmt.Printf("Encrypted JWE: %s\n", encrypted)
}

解密数据

func decryptJWE(jweStr string) {
	privateKey := `-----BEGIN PRIVATE KEY-----
... your private key here ...
-----END PRIVATE KEY-----`

	decrypted, err := jwe.Decrypt(
		[]byte(jweStr),
		jwe.WithKey(jwa.RSA_OAEP, []byte(privateKey)),
	)
	if err != nil {
		fmt.Printf("failed to decrypt JWE: %s\n", err)
		return
	}

	fmt.Printf("Decrypted payload: %s\n", decrypted)
}

JWK (JSON Web Key) 示例

生成和解析JWK

package main

import (
	"fmt"
	
	"github.com/lestrrat-go/jwx/v2/jwk"
)

func main() {
	// 生成RSA密钥对
	key, err := jwk.GenerateKey(jwk.WithKeyType(jwa.RSA))
	if err != nil {
		fmt.Printf("failed to generate key: %s\n", err)
		return
	}

	// 设置密钥ID
	_ = key.Set(jwk.KeyIDKey, "my-key-id")

	// 将密钥序列化为JSON
	jsonBuf, err := jwk.Encode(key)
	if err != nil {
		fmt.Printf("failed to encode key: %s\n", err)
		return
	}

	fmt.Printf("JWK: %s\n", jsonBuf)

	// 从JSON解析JWK
	parsedKey, err := jwk.ParseKey(jsonBuf)
	if err != nil {
		fmt.Printf("failed to parse key: %s\n", err)
		return
	}

	fmt.Printf("Parsed key type: %s\n", parsedKey.KeyType())
}

最佳实践

  1. 密钥管理:永远不要在代码中硬编码密钥,使用环境变量或密钥管理系统
  2. 算法选择
    • 签名:优先选择ES256/ES384/ES512或PS256/PS384/PS512
    • 加密:优先选择RSA-OAEP-256或ECDH-ES+A256KW
  3. 令牌有效期:总是设置合理的过期时间
  4. 错误处理:正确处理所有错误,不要泄露敏感信息
  5. 密钥轮换:实现密钥轮换机制,定期更新密钥

jwx库提供了丰富的功能来处理JOSE标准,上述示例展示了最常见的用例。根据你的具体需求,你可能需要查阅官方文档来了解更多高级功能。

回到顶部