golang轻量级JSON Web Token(JWT)生成与验证插件库jwt的使用

golang轻量级JSON Web Token(JWT)生成与验证插件库jwt的使用

关于

这是一个用于Go编程语言的JSON Web Token(JWT)库。

特点:

  • 功能完整
  • 完全测试覆盖
  • 无依赖
  • 密钥管理

API设计强制安全使用,拒绝未签名的令牌,也不支持加密令牌,建议使用线路加密替代。

简介

令牌封装了称为声明的签名声明。声明是一个命名的JSON值。使用JWT的应用程序应定义它们使用哪些特定声明以及何时是必需或可选的。

var claims jwt.Claims
claims.Subject = "alice"
claims.Issued  = jwt.NewNumericTime(time.Now().Round(time.Second))
claims.Set     = map[string]interface{}{"email_verified": false}
// 签发JWT
token, err := claims.EdDSASign(JWTPrivateKey)

令牌由可打印的ASCII字符组成,例如: eyJhbGciOiJFUzI1NiJ9.eyJzdWIiOiJha3JpZWdlciIsInByZWZpeCI6IkRyLiJ9.RTOboYsLW7zXFJyXtIypOmXfuRGVT_FpDUTs2TOuK73qZKm56JcESfsl_etnBsl7W80TXE5l5qecrMizh3XYmw

// 验证JWT
claims, err := jwt.EdDSACheck(token, JWTPublicKey)
if err != nil {
	log.Print("credentials rejected: ", err)
	return
}
err = claims.AcceptTemporal(time.Now(), time.Second)
if err != nil {
	log.Print("credential constraints violated: ", err)
	return
}

// 准备使用
log.Print("hello ", claims.Subject)
if verified, _ := claims.Set["email_verified"].(bool); !verified {
	log.Print("e-mail confirmation pending")
}

高级API

服务器端安全性可以通过标准的http.Handler设置应用。以下示例在JWT无效或JWT缺少主题、格式化名称或角色声明时拒绝请求。

// 定义可信凭据
var keys jwt.KeyRegister
n, err := keys.LoadPEM(text, nil)
if err != nil {
	log.Fatal(err)
}
log.Print("setup with ", n, " JWT keys")

http.Handle("/api/v1", &jwt.Handler{
	Target: MyAPI, // 受保护的HTTP处理程序
	Keys:   &keys,

	// 将两个声明映射到HTTP头
	HeaderPrefix: "X-Verified-",
	HeaderBinding: map[string]string{
		"sub": "X-Verified-User", // 注册[标准]声明
		"fn":  "X-Verified-Name", // 私有[自定义]声明
	},

	// 使用自定义逻辑映射另一个声明
	Func: func(w http.ResponseWriter, req *http.Request, claims *jwt.Claims) (pass bool) {
		log.Printf("got a valid JWT %q for %q", claims.ID, claims.Audiences)

		// 映射角色枚举
		s, ok := claims.String("roles")
		if !ok {
			http.Error(w, "jwt: want roles claim as a string", http.StatusForbidden)
			return false
		}
		req.Header["X-Verified-Roles"] = strings.Fields(s)

		return true
	},
})

当所有适用的JWT声明都映射到HTTP请求头时,服务逻辑可以保持无验证代码,并且更容易进行单元测试。

// Greeting是一个标准的HTTP处理函数。
func Greeting(w http.ResponseWriter, req *http.Request) {
	fmt.Fprintf(w, "Hello %s!\n", req.Header.Get("X-Verified-Name"))
	fmt.Fprintf(w, "You are authorized as %s.\n", req.Header.Get("X-Verified-User"))
}

性能

以下结果是在Apple M1上使用Go 1.20.3测量的。

ECDSA/sign-ES256-8         19.88µ ± 0%
ECDSA/sign-ES384-8         182.2µ ± 0%
ECDSA/check-ES256-8        58.65µ ± 0%
ECDSA/check-ES384-8        535.2µ ± 0%
EdDSA/sign-EdDSA-8         21.30µ ± 1%
EdDSA/check-EdDSA-8        47.12µ ± 1%
HMAC/sign-HS256-8          660.1n ± 0%
HMAC/sign-HS256-reuse-8    458.3n ± 1%
HMAC/sign-HS384-8          1.028µ ± 0%
HMAC/sign-HS384-reuse-8    600.4n ± 0%
HMAC/sign-HS512-8          1.053µ ± 0%
HMAC/sign-HS512-reuse-8    616.6n ± 0%
HMAC/check-HS256-8         1.826µ ± 0%
HMAC/check-HS256-reuse-8   1.611µ ± 1%
HMAC/check-HS384-8         2.271µ ± 1%
HMAC/check-HS384-reuse-8   1.786µ ± 1%
HMAC/check-HS512-8         2.287µ ± 1%
HMAC/check-HS512-reuse-8   1.803µ ± 0%
RSA/sign-1024-bit-8        292.8µ ± 1%
RSA/sign-2048-bit-8        1.273m ± 0%
RSA/sign-4096-bit-8        8.685m ± 1%
RSA/check-1024-bit-8       49.51µ ± 3%
RSA/check-2048-bit-8       168.6µ ± 0%
RSA/check-4096-bit-8       662.6µ ± 0%

EdDSA [Ed25519]产生小的签名并且性能良好。

标准合规

  • RFC 2617: “HTTP Authentication”
  • RFC 6750: “The OAuth 2.0 Authorization Framework: Bearer Token Usage”
  • RFC 7468: “Textual Encodings of PKIX, PKCS, and CMS Structures”
  • RFC 7515: “JSON Web Signature (JWS)”
  • RFC 7517: “JSON Web Key (JWK)”
  • RFC 7518: “JSON Web Algorithms (JWA)”
  • RFC 7519: “JSON Web Token (JWT)”
  • RFC 8037: “CFRG Elliptic Curve Diffie-Hellman (ECDH) and Signatures in JSON Object Signing and Encryption (JOSE)”

更多关于golang轻量级JSON Web Token(JWT)生成与验证插件库jwt的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang轻量级JSON Web Token(JWT)生成与验证插件库jwt的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang轻量级JWT生成与验证指南

JWT(JSON Web Token)是一种流行的跨域认证解决方案,下面我将介绍如何在Go中使用轻量级的jwt库进行JWT的生成与验证。

推荐库

在Go生态中,最常用的JWT库是github.com/golang-jwt/jwt(原github.com/dgrijalva/jwt-go),它是一个轻量级且功能完整的实现。

go get github.com/golang-jwt/jwt/v5

基本使用示例

1. 生成JWT Token

package main

import (
	"fmt"
	"time"
	
	"github.com/golang-jwt/jwt/v5"
)

// 自定义Claims结构体
type CustomClaims struct {
	UserID   int    `json:"user_id"`
	Username string `json:"username"`
	jwt.RegisteredClaims
}

func GenerateJWT(userID int, username string, secretKey string) (string, error) {
	// 创建Claims
	claims := CustomClaims{
		UserID:   userID,
		Username: username,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), // 过期时间
			IssuedAt:  jwt.NewNumericDate(time.Now()),                    // 签发时间
			NotBefore: jwt.NewNumericDate(time.Now()),                    // 生效时间
			Issuer:    "myapp",                                           // 签发者
			Subject:   "user-auth",                                       // 主题
		},
	}

	// 创建Token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	// 使用密钥签名
	signedToken, err := token.SignedString([]byte(secretKey))
	if err != nil {
		return "", fmt.Errorf("生成JWT失败: %v", err)
	}

	return signedToken, nil
}

func main() {
	secretKey := "your-256-bit-secret"
	token, err := GenerateJWT(123, "john_doe", secretKey)
	if err != nil {
		fmt.Println("生成Token失败:", err)
		return
	}
	
	fmt.Println("生成的JWT Token:", token)
}

2. 验证JWT Token

func ParseJWT(tokenString string, secretKey string) (*CustomClaims, error) {
	// 解析Token
	token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
		// 验证签名方法
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("意外的签名方法: %v", token.Header["alg"])
		}
		return []byte(secretKey), nil
	})

	if err != nil {
		return nil, fmt.Errorf("解析JWT失败: %v", err)
	}

	// 验证Claims
	if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
		return claims, nil
	}

	return nil, fmt.Errorf("无效的JWT Token")
}

func main() {
	// 假设这是从客户端收到的Token
	tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjMsInVzZXJuYW1lIjoiam9obl9kb2UiLCJleHAiOjE2ODA5MzQwMDAsImlzcyI6Im15YXBwIiwic3ViIjoidXNlci1hdXRoIn0.abcdef1234567890" // 示例Token,实际使用时替换
	
	secretKey := "your-256-bit-secret"
	claims, err := ParseJWT(tokenString, secretKey)
	if err != nil {
		fmt.Println("验证Token失败:", err)
		return
	}
	
	fmt.Printf("验证成功! 用户ID: %d, 用户名: %s\n", claims.UserID, claims.Username)
}

高级用法

1. 使用RS256非对称加密

// 生成RSA密钥对
// openssl genrsa -out private.pem 2048
// openssl rsa -in private.pem -pubout -out public.pem

func GenerateRSAJWT(userID int, username string, privateKeyPath string) (string, error) {
	privateKeyBytes, err := os.ReadFile(privateKeyPath)
	if err != nil {
		return "", err
	}

	privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyBytes)
	if err != nil {
		return "", err
	}

	claims := CustomClaims{
		UserID:   userID,
		Username: username,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
	return token.SignedString(privateKey)
}

func VerifyRSAJWT(tokenString string, publicKeyPath string) (*CustomClaims, error) {
	publicKeyBytes, err := os.ReadFile(publicKeyPath)
	if err != nil {
		return nil, err
	}

	publicKey, err := jwt.ParseRSAPublicKeyFromPEM(publicKeyBytes)
	if err != nil {
		return nil, err
	}

	token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
			return nil, fmt.Errorf("意外的签名方法: %v", token.Header["alg"])
		}
		return publicKey, nil
	})

	if err != nil {
		return nil, err
	}

	if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
		return claims, nil
	}

	return nil, fmt.Errorf("无效的JWT Token")
}

2. 刷新Token

func RefreshJWT(tokenString string, secretKey string) (string, error) {
	claims, err := ParseJWT(tokenString, secretKey)
	if err != nil {
		return "", err
	}

	// 检查Token是否即将过期(例如在30分钟内过期)
	if time.Until(claims.ExpiresAt.Time) > 30*time.Minute {
		return "", fmt.Errorf("token尚未接近过期")
	}

	// 更新过期时间
	claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(24 * time.Hour))
	
	newToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return newToken.SignedString([]byte(secretKey))
}

最佳实践

  1. 密钥管理:不要将密钥硬编码在代码中,使用环境变量或配置管理系统
  2. 过期时间:设置合理的过期时间(通常几小时到几天)
  3. 敏感数据:不要在JWT中存储敏感信息,因为JWT可以被解码
  4. HTTPS:始终通过HTTPS传输JWT
  5. 存储方式:客户端通常将JWT存储在localStorage或cookie中

这个轻量级的JWT实现可以满足大多数应用场景的需求,既保证了安全性又保持了简单性。

回到顶部