Go JWT 认证中间件 — gin + golang-jwt

知识库
知识库文档
/tech-stacks/golang/examples/Go JWT 认证中间件 — gin + golang-jwt.md

文档

Go JWT 认证中间件

目标

golang-jwt/jwt 实现 Token 签发和验证,作为 Gin 中间件保护私有 API。

完整代码

package main

import (
	"errors"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
)

var jwtSecret = []byte("your-secret-key-change-in-production")

type Claims struct {
	UserID   int    `json:"userId"`
	Username string `json:"username"`
	Role     string `json:"role"`
	jwt.RegisteredClaims
}

// GenerateToken 签发 Token
func GenerateToken(userID int, username, role string) (string, error) {
	claims := Claims{
		UserID:   userID,
		Username: username,
		Role:     role,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(7 * 24 * time.Hour)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			Issuer:    "my-bishe",
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(jwtSecret)
}

// ParseToken 解析并验证 Token
func ParseToken(tokenStr string) (*Claims, error) {
	token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(t *jwt.Token) (any, error) {
		if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, errors.New("无效签名算法")
		}
		return jwtSecret, nil
	})
	if err != nil {
		return nil, err
	}
	claims, ok := token.Claims.(*Claims)
	if !ok || !token.Valid {
		return nil, errors.New("无效 Token")
	}
	return claims, nil
}

// AuthMiddleware JWT 认证中间件
func AuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		tokenStr := c.GetHeader("Authorization")
		if len(tokenStr) < 8 || tokenStr[:7] != "Bearer " {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "未提供 Token"})
			return
		}
		claims, err := ParseToken(tokenStr[7:])
		if err != nil {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Token 无效或已过期"})
			return
		}
		c.Set("userId", claims.UserID)
		c.Set("username", claims.Username)
		c.Set("role", claims.Role)
		c.Next()
	}
}

// RoleMiddleware 角色权限中间件(工厂函数)
func RoleMiddleware(roles ...string) gin.HandlerFunc {
	return func(c *gin.Context) {
		role, _ := c.Get("role")
		for _, r := range roles {
			if role == r {
				c.Next()
				return
			}
		}
		c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "无权访问"})
	}
}

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

	// 公开接口:登录获取 Token
	r.POST("/login", func(c *gin.Context) {
		var req struct {
			Username string `json:"username"`
			Password string `json:"password"`
		}
		c.ShouldBindJSON(&req)

		// 模拟验证(实际应查数据库)
		if req.Username == "admin" && req.Password == "123456" {
			token, _ := GenerateToken(1, req.Username, "admin")
			c.JSON(200, gin.H{"token": token, "expiresIn": "7天"})
		} else {
			c.JSON(401, gin.H{"error": "账号或密码错误"})
		}
	})

	// 受保护路由组
	auth := r.Group("/api").Use(AuthMiddleware())
	{
		auth.GET("/profile", func(c *gin.Context) {
			c.JSON(200, gin.H{
				"userId":   c.GetInt("userId"),
				"username": c.GetString("username"),
				"role":     c.GetString("role"),
			})
		})

		// 仅管理员可访问
		admin := auth.Group("/admin").Use(RoleMiddleware("admin"))
		{
			admin.GET("/dashboard", func(c *gin.Context) {
				c.JSON(200, gin.H{"message": "管理员面板"})
			})
		}
	}

	r.Run(":8080")
}

测试

# 登录获取 Token
curl -X POST http://localhost:8080/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"123456"}'

# 使用 Token 访问受保护接口
curl http://localhost:8080/api/profile \
  -H "Authorization: Bearer <TOKEN>"

# 非管理员访问 admin 接口 → 403

关键要点

  • HS256 对称加密,简单够用;生产环境用 RS256(非对称)
  • Token 存在客户端,服务端不存储状态
  • 过期时间设置 7 天,可选实现 Refresh Token 续期

信息

路径
/tech-stacks/golang/examples/Go JWT 认证中间件 — gin + golang-jwt.md
更新时间
2026/5/30