Go 中最主流 JWT 库 jwt -go

Go 中最主流 JWT 库 jwt -go

JWT(JSON Web Token)是目前前后端分离项目中最主流的身份认证方案,它通过在客户端存储加密 Token,实现了无状态的身份验证,完美适配分布式系统和微服务架构。

  1. 自定义声明 :通过嵌入 jwt.RegisteredClaims 定义 Token 中的数据。
  2. 生成 Token :使用 jwt.NewWithClaims()SignedString() 生成 Token。
  3. 解析 Token :使用 jwt.ParseWithClaims() 解析并验证 Token。
  4. 中间件集成:作为 Gin 中间件保护受保护接口。
  5. Refresh Token:实现无感刷新 Token,提升用户体验。

什么是 JWT

JWT 是一种开放标准(RFC 7519),它通过 JSON 对象在各方之间安全地传输信息,这些信息可以被验证和信任,因为它是数字签名的。

JWT 的结构 JWT 由三部分组成,用点(.)分隔。

示例:xxxxx.yyyyy.zzzzz

  • Header(头部) :包含令牌类型(JWT)和签名算法(如 HS256)
  • Payload(载荷) :包含声明(Claims),即实际传输的数据(如用户 ID、过期时间)
  • Signature(签名) :使用密钥对 Header 和 Payload 进行签名,防止数据被篡改

JWT 的工作流程

  1. 用户登录成功后,服务器生成 JWT Token 并返回给客户端
  2. 客户端将 Token 存储在本地(如 LocalStorage、Cookie)
  3. 客户端后续请求时在请求头(如 Authorization: Bearer <token>)中携带 Token
  4. 服务器验证 Token 的有效性,验证通过后允许访问受保护资源

核心 API 详解

自定义声明:定义 Token 中的数据

JWT 的 Payload 部分通过声明存储数据,jwt-go 提供了标准声明,同时支持自定义声明,这是 JWT 开发的第一步。

自定义声明结构体

go 复制代码
// 必须嵌入 jwt.RegisteredClaims,它包含了 JWT 的标准声明
type UserClaims struct {
	
   // 自定义声明:根据业务需求添加
	UserID   uint   `json:"user_id"`
	Username string `json:"username"`
        
	// 嵌入标准声明
	jwt.RegisteredClaims
}

jwt.RegisteredClaims 包含了 JWT 的标准声明

字段 作用 示例
Issuer 签发者 "your_app_name"
Subject 主题 "user_auth"
Audience 受众 "your_client"
ExpiresAt 过期时间 jwt.NewNumericDate(time.Now().Add(2 * time.Hour))
NotBefore 生效时间 jwt.NewNumericDate(time.Now())
IssuedAt 签发时间 jwt.NewNumericDate(time.Now())
ID 唯一标识 uuid.NewString()

生成 Token:创建 JWT

用户登录成功后,服务器需要生成 JWT Token 并返回给客户端,这是 JWT 开发的核心功能。

API 作用
jwt.NewWithClaims(method, claims) 创建 Token 对象,指定签名算法和声明
token.SignedString(secretKey) 使用密钥对 Token 进行签名,生成最终的 Token 字符串

生成 JWT Token

go 复制代码
// JWT 密钥(生产环境必须存储在环境变量或配置文件中,严禁硬编码!)
var JwtSecret = []byte("your_jwt_secret_key_change_this_in_production")

func GenerateToken(userId uint, username string) (string, error) {
	// 创建声明
	claims := UserClaims{
		UserID:   userId,
		Username: username,
		RegisteredClaims: jwt.RegisteredClaims{
			Issuer:    "gin_demo",          // 签发者
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(2 * time.Hour)), // 过期时间:2小时后
			NotBefore: jwt.NewNumericDate(time.Now()), // 生效时间:立即生效
			IssuedAt:  jwt.NewNumericDate(time.Now()), // 签发时间:现在
			ID:        fmt.Sprintf("%d", userId), // 唯一标识
		},
	}

	// 创建 Token 对象,使用 HS256 签名算法
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	// 使用密钥签名,生成最终的 Token 字符串
	tokenString, err := token.SignedString(JwtSecret)
	if err != nil {
		return "", fmt.Errorf("生成 Token 失败:%v", err)
	}

	return tokenString, nil
}
  • 密钥安全:JWT 密钥必须严格保密,生产环境必须存储在环境变量、配置中心或密钥管理服务中,严禁硬编码在代码里!
  • 密钥强度:密钥长度建议至少 32 位,使用随机字符串,避免使用弱密钥。
  • 过期时间 :必须设置 ExpiresAt,建议 Access Token 有效期为 1-2 小时,Refresh Token 有效期为 7-30 天。

解析 Token:验证 JWT 有效性

客户端请求时携带 Token,服务器需要解析并验证 Token 的有效性,这是 JWT 开发的另一个核心功能。

API 作用
jwt.ParseWithClaims(tokenString, &claims, keyFunc) 解析 Token 并验证签名,同时将声明绑定到自定义结构体
token.Valid 判断 Token 是否有效(签名正确、未过期、已生效)

解析并验证 JWT Token

go 复制代码
func ParseToken(tokenString string) (*UserClaims, error) {
	// 解析 Token
	claims := &UserClaims{}
	token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (any, error) {
		// 验证签名算法是否为 HS256(防止算法替换攻击)
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("签名算法不匹配:%v", token.Header["alg"])
		}
		// 返回密钥
		return JwtSecret, nil
	})

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

	// 验证 Token 是否有效
	if !token.Valid {
		return nil, fmt.Errorf("Token 无效")
	}

	return claims, nil
}
  • 验证签名算法 :必须在 keyFunc 中验证签名算法是否为预期的算法(如 HS256),防止算法替换攻击。
  • 错误处理:解析 Token 时可能出现多种错误(签名错误、过期、未生效等),需根据错误类型返回不同的提示信息。

Gin 中间件集成

JWT 最常用的场景是作为 Gin 中间件,保护需要登录才能访问的接口,这是企业开发的必用功能。

JWT 鉴权中间件

go 复制代码
func JWTAuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 1. 从请求头获取 Token
		// 格式:Authorization: Bearer <token>
		authHeader := c.GetHeader("Authorization")
		if authHeader == "" {
			c.JSON(http.StatusUnauthorized, gin.H{
				"code": 401,
				"msg":  "请先登录",
			})
			c.Abort() // 终止请求
			return
		}

		// 2. 解析 Bearer 前缀
		var tokenString string
		_, err := fmt.Sscanf(authHeader, "Bearer %s", &tokenString)
		if err != nil {
			c.JSON(http.StatusUnauthorized, gin.H{
				"code": 401,
				"msg":  "Token 格式错误",
			})
			c.Abort()
			return
		}

		// 3. 解析并验证 Token
		claims, err := ParseToken(tokenString)
		if err != nil {
			c.JSON(http.StatusUnauthorized, gin.H{
				"code": 401,
				"msg":  "Token 无效或已过期",
			})
			c.Abort()
			return
		}

		// 4. 将用户信息存入 Gin 上下文,后续处理函数可获取
		c.Set("userId", claims.UserID)
		c.Set("username", claims.Username)

		// 5. 继续执行后续处理
		c.Next()
	}
}

func main() {
	r := gin.Default()

	// 公开接口:无需登录
	r.POST("/login", func(c *gin.Context) {
		..........................................
           // 模拟登录
	})

	// 受保护接口:需要登录
	authGroup := r.Group("/api")
	authGroup.Use(JWTAuthMiddleware()) // 应用 JWT 鉴权中间件
	{
		..........................................
           // 模拟处理逻辑与返回信息
	}

	r.Run(":8080")
}

刷新 Token

为了安全性,Access Token(访问令牌)的有效期通常较短(如 2 小时),但用户不希望每 2 小时就重新登录一次,这时就需要 Refresh Token(刷新令牌)。

核心思路

  1. 登录时同时生成 Access Token(有效期 2 小时)和 Refresh Token(有效期 7 天)
  2. Access Token 用于访问受保护接口
  3. Access Token 过期后,使用 Refresh Token 换取新的 Access Token
  4. Refresh Token 过期后,用户需要重新登录
go 复制代码
// 生成 Access Token 和 Refresh Token
func GenerateTokens(userId uint, username string) (accessToken, refreshToken string, err error) {
	// Access Token:有效期 2 小时
	accessClaims := UserClaims{
		UserID:   userId,
		Username: username,
		RegisteredClaims: jwt.RegisteredClaims{
			Issuer:    "gin_demo",
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(2 * time.Hour)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
		},
	}
	accessToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims).SignedString(JwtSecret)
	if err != nil {
		return
	}

	// Refresh Token:有效期 7 天
	refreshClaims := jwt.RegisteredClaims{
		Issuer:    "gin_demo",
		ExpiresAt: jwt.NewNumericDate(time.Now().Add(7 * 24 * time.Hour)),
		IssuedAt:  jwt.NewNumericDate(time.Now()),
		ID:        fmt.Sprintf("%d", userId),
	}
	refreshToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims).SignedString(JwtSecret)
	return
}

注意事项

  • 使用 HTTPS:JWT Token 必须通过 HTTPS 传输,防止被中间人窃取。
  • Token 存储:客户端建议将 Token 存储在 HttpOnly Cookie 中,防止 XSS 攻击;如存储在 LocalStorage,需做好 XSS 防护。
  • 设置合理的过期时间:Access Token 有效期建议 1-2 小时,Refresh Token 有效期建议 7-30 天。
  • 使用强密钥:密钥长度至少 32 位,使用随机字符串,定期更换密钥。
  • 验证签名算法:解析 Token 时必须验证签名算法,防止算法替换攻击。
  • 黑名单机制:如需主动注销 Token,可使用 Redis 实现 Token 黑名单。
  • 不要在 Token 中存储敏感信息:JWT 的 Payload 部分仅 Base64 编码,未加密,任何人都可以解码查看,严禁存储密码、身份证号等敏感信息。
  • 密钥必须保密:密钥泄露会导致攻击者可以伪造任意 Token,生产环境必须严格保管密钥。
  • 防止重放攻击 :可在 Token 中添加 jti(JWT ID)声明,结合 Redis 实现一次性 Token 或短时间内防重放。
相关推荐
红尘散仙6 小时前
我把终端小说阅读器接上了 AI Agent:TRNovel 现在能用 skill 生成书源了
人工智能·后端·rust
卷毛的技术笔记7 小时前
告别硬编码!Spring AI Alibaba 实现 AI Agent 智能工具调用(Tool Calling)
java·人工智能·后端·python·spring·ai编程
isyangli_blog7 小时前
OpenDayLight (Carbon 版本) 启动与组件安装
开发语言·php
vb2008117 小时前
FastAPI APIRouter
开发语言·python
Benszen7 小时前
KVM虚拟化解决方案
开发语言·perl
会编程的土豆7 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
東雪木7 小时前
多线程与并发编程 专属复习笔记
java·开发语言·笔记·java面试
喵个咪8 小时前
GoWind Toolkit Go后端代码生成 完整全流程实战
后端·go·orm
杨充8 小时前
1.3 浮点型数据设计灵魂
开发语言·python·算法
噜噜噜阿鲁~8 小时前
python学习笔记 | 11.3、面向对象高级编程-多重继承
java·开发语言