全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-18 10 分钟 ✍️ juanwangdev

Gin JWT认证与鉴权

JWT(JSON Web Token)是无状态认证的标准方案,适合分布式系统和 API 认证。

JWT 结构

JWT 由三部分组成:Header.Payload.Signature

Bash
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE2ODAwMDAwMDB9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
部分说明内容
Header算法信息{"alg":"HS256","typ":"JWT"}
Payload用户数据{"user_id":1,"exp":1680000000}
Signature签名防篡改验证

安装依赖

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

JWT 生成与验证

基础实现

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

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

type Claims struct {
    UserID   uint   `json:"user_id"`
    Username string `json:"username"`
    jwt.RegisteredClaims
}

// 生成 Token
func GenerateToken(userID uint, username string) (string, error) {
    claims := Claims{
        UserID:   userID,
        Username: username,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
            Issuer:    "gin-app",
        },
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString(jwtSecret)
}

// 验证 Token
func ParseToken(tokenString string) (*Claims, error) {
    token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
        return jwtSecret, nil
    })

    if err != nil {
        return nil, err
    }

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

    return nil, jwt.ErrSignatureInvalid
}

Gin 中间件实现

JWT 认证中间件

Go
func JWTAuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 从 Header 获取 Token
        authHeader := c.GetHeader("Authorization")
        if authHeader == "" {
            c.JSON(401, gin.H{"error": "未提供认证令牌"})
            c.Abort()
            return
        }

        // Bearer Token 格式
        parts := strings.SplitN(authHeader, " ", 2)
        if !(len(parts) == 2 && parts[0] == "Bearer") {
            c.JSON(401, gin.H{"error": "认证格式错误"})
            c.Abort()
            return
        }

        // 验证 Token
        claims, err := ParseToken(parts[1])
        if err != nil {
            c.JSON(401, gin.H{"error": "无效的认证令牌"})
            c.Abort()
            return
        }

        // 存储用户信息到上下文
        c.Set("user_id", claims.UserID)
        c.Set("username", claims.Username)
        c.Next()
    }
}

路由使用

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

    // 公开路由
    r.POST("/login", LoginHandler)
    r.POST("/register", RegisterHandler)

    // 需要认证的路由
    authorized := r.Group("/api")
    authorized.Use(JWTAuthMiddleware())
    {
        authorized.GET("/profile", ProfileHandler)
        authorized.PUT("/profile", UpdateProfileHandler)
    }

    r.Run(":8080")
}

登录流程实现

Go
type LoginRequest struct {
    Username string `json:"username" binding:"required"`
    Password string `json:"password" binding:"required"`
}

func LoginHandler(c *gin.Context) {
    var req LoginRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(400, gin.H{"error": "参数错误"})
        return
    }

    // 验证用户名密码
    user, err := authenticateUser(req.Username, req.Password)
    if err != nil {
        c.JSON(401, gin.H{"error": "用户名或密码错误"})
        return
    }

    // 生成 Token
    token, err := GenerateToken(user.ID, user.Username)
    if err != nil {
        c.JSON(500, gin.H{"error": "生成令牌失败"})
        return
    }

    c.JSON(200, gin.H{
        "token": token,
        "user": gin.H{
            "id":       user.ID,
            "username": user.Username,
        },
    })
}

Token 刷新机制

Go
// 双 Token 方案:Access Token + Refresh Token
func GenerateTokenPair(userID uint, username string) (accessToken, refreshToken string, err error) {
    // 短期 Access Token (15分钟)
    accessClaims := Claims{
        UserID:   userID,
        Username: username,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
        },
    }
    accessToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims).SignedString(jwtSecret)
    if err != nil {
        return "", "", err
    }

    // 长期 Refresh Token (7天)
    refreshClaims := jwt.RegisteredClaims{
        Subject:   string(rune(userID)),
        ExpiresAt: jwt.NewNumericDate(time.Now().Add(7 * 24 * time.Hour)),
        IssuedAt:  jwt.NewNumericDate(time.Now()),
    }
    refreshToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims).SignedString(jwtSecret)

    return accessToken, refreshToken, err
}

// 刷新接口
func RefreshTokenHandler(c *gin.Context) {
    refreshToken := c.PostForm("refresh_token")
    if refreshToken == "" {
        c.JSON(400, gin.H{"error": "缺少 refresh token"})
        return
    }

    token, err := jwt.ParseWithClaims(refreshToken, &jwt.RegisteredClaims{}, func(token *jwt.Token) (interface{}, error) {
        return jwtSecret, nil
    })

    if err != nil || !token.Valid {
        c.JSON(401, gin.H{"error": "无效的 refresh token"})
        return
    }

    // 生成新的 Access Token
    claims := token.Claims.(*jwt.RegisteredClaims)
    userID := uint([]rune(claims.Subject)[0])

    newAccessToken, err := GenerateToken(userID, "")
    if err != nil {
        c.JSON(500, gin.H{"error": "生成令牌失败"})
        return
    }

    c.JSON(200, gin.H{
        "access_token": newAccessToken,
    })
}

安全配置

Go
import (
    "crypto/sha256"
    "encoding/base64"
)

// 使用更强的签名密钥
func generateSecureSecret() []byte {
    // 生产环境应从配置或环境变量读取
    secret := os.Getenv("JWT_SECRET")
    if secret == "" {
        panic("JWT_SECRET 环境变量未设置")
    }
    hash := sha256.Sum256([]byte(secret))
    return hash[:]
}

// Token 黑名单(登出失效)
type TokenBlacklist struct {
    tokens map[string]time.Time
    mu     sync.RWMutex
}

var blacklist = &TokenBlacklist{
    tokens: make(map[string]time.Time),
}

func (b *TokenBlacklist) Add(token string, exp time.Time) {
    b.mu.Lock()
    defer b.mu.Unlock()
    b.tokens[token] = exp
}

func (b *TokenBlacklist) Contains(token string) bool {
    b.mu.RLock()
    defer b.mu.RUnlock()
    _, exists := b.tokens[token]
    return exists
}

func (b *TokenBlacklist) Cleanup() {
    b.mu.Lock()
    defer b.mu.Unlock()
    now := time.Now()
    for token, exp := range b.tokens {
        if exp.Before(now) {
            delete(b.tokens, token)
        }
    }
}

// 登出处理
func LogoutHandler(c *gin.Context) {
    authHeader := c.GetHeader("Authorization")
    if authHeader != "" {
        parts := strings.SplitN(authHeader, " ", 2)
        if len(parts) == 2 {
            blacklist.Add(parts[1], time.Now().Add(24*time.Hour))
        }
    }
    c.JSON(200, gin.H{"message": "登出成功"})
}

最佳实践

text
func SecurityMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 检查黑名单
        authHeader := c.GetHeader("Authorization")
        if authHeader != "" {
            parts := strings.SplitN(authHeader, " ", 2)
            if len(parts) == 2 && blacklist.Contains(parts[1]) {
                c.JSON(401, gin.H{"error": "令牌已失效"})
                c.Abort()
                return
            }
        }
        c.Next()
    }
}

JWT 配置对比

配置项推荐值说明
签名算法HS256/RS256RS256 安全性更高
Access Token 有效期15-30分钟短期有效降低风险
Refresh Token 有效期7天-30天便于用户使用
密钥长度256位+防止暴力破解

注意:生产环境密钥应从环境变量读取,禁止硬编码。

要点总结

  1. Token 生成:包含用户 ID、过期时间,使用安全密钥签名
  2. 中间件验证:解析 Token 并将用户信息存入上下文
  3. 双 Token 方案:Access Token 短期 + Refresh Token 长期
  4. 安全措施:黑名单机制、安全密钥、HTTPS 传输
  5. 密钥管理:使用环境变量,禁止代码硬编码

📝 发现内容有误?点击此处直接编辑

← 上一篇 Gin HTTPS与证书配置
下一篇 → Gin RBAC权限模型实现
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库