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
更多关于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))
}
最佳实践
- 密钥管理:不要将密钥硬编码在代码中,使用环境变量或配置管理系统
- 过期时间:设置合理的过期时间(通常几小时到几天)
- 敏感数据:不要在JWT中存储敏感信息,因为JWT可以被解码
- HTTPS:始终通过HTTPS传输JWT
- 存储方式:客户端通常将JWT存储在localStorage或cookie中
这个轻量级的JWT实现可以满足大多数应用场景的需求,既保证了安全性又保持了简单性。