欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go實現(xiàn)JWT認證中間件的項目實戰(zhàn)

 更新時間:2025年08月08日 09:51:28   作者:Vespeng  
本文將介紹在Gin框架中實現(xiàn)完整的JWT認證方案,同時包含靈活的?Redis?集成選項,文中通過示例代碼介紹的非常詳細,需要的朋友們下面隨著小編來一起學習學習吧

在構(gòu)建安全可靠的 Go Web 服務(wù)時,JWT(JSON Web Token)認證是常用的解決方案。本文將介紹如何在 Gin 框架中實現(xiàn)完整的 JWT 認證方案,同時包含靈活的 Redis 集成選項。

一、為什么需要 JWT 中間件

JWT 作為現(xiàn)代 Web 開發(fā)的認證標準,相比傳統(tǒng) cookie + session 方式有幾個明顯優(yōu)勢:

  • 無狀態(tài)性:服務(wù)器不需要存儲會話信息
  • 跨域支持:天然支持跨域認證
  • 安全傳輸:基于簽名機制防止篡改
  • 信息自包含:Token 本身攜帶用戶信息

在 Gin 框架中通過中間件實現(xiàn) JWT 認證,可以統(tǒng)一處理認證邏輯,避免每個路由重復(fù)編寫驗證代碼。

二、核心依賴包

開始前需要安裝如下包:

go get github.com/gin-gonic/gin
go get github.com/golang-jwt/jwt/v5
go get github.com/redis/go-redis/v9  # 可選,按需安裝

三、實現(xiàn)方案設(shè)計

在實現(xiàn) JWT 認證中間件時,我們的設(shè)計方案需要兼顧靈活性和安全性。整個流程可以分為幾個關(guān)鍵步驟:

  1. 初始化配置:從配置文件或環(huán)境變量中加載 JWT 的配置(如密鑰、簽發(fā)者、簽名算法、過期時間等)。我們使用單例模式確保配置只加載一次,并通過互斥鎖保證并發(fā)安全。
  2. 中間件流程
    • 排除特定路由:對于不需要認證的路由(如登錄、公開資源),直接跳過 JWT 驗證。
    • 解析 Authorization 頭:從請求頭中提取 Bearer Token,并驗證其格式是否正確。
    • 驗證 Token:根據(jù)是否啟用 Redis,采用不同的驗證方式:
      • 如果啟用了 Redis,首先嘗試從 Redis 中獲取該 Token 對應(yīng)的聲明(claims)。如果存在且有效,則直接使用;如果不存在或無效,則回退到JWT庫的驗證方式。
      • 如果沒有啟用 Redis,則直接使用 JWT 庫驗證 Token 的簽名和有效期。
    • 處理驗證結(jié)果:如果驗證通過,將 claims 存儲到 Gin 的上下文中,供后續(xù)處理函數(shù)使用;如果驗證失敗,則根據(jù)具體的錯誤類型返回相應(yīng)的錯誤信息。
  3. Token 生成:在用戶登錄成功后,生成 JWT Token。Token 中包含用戶的身份信息(如用戶ID和用戶名)以及 JWT 的標準聲明(如過期時間、簽發(fā)者等)。如果啟用了 Redis,還需要將 Token 和對應(yīng)的聲明存儲到 Redis 中,并設(shè)置與Token相同的過期時間。
  4. 錯誤處理:針對 JWT 驗證過程中可能出現(xiàn)的錯誤(如 Token 過期、格式錯誤、簽名無效等),提供清晰的錯誤信息,方便前端處理。
  5. 配置管理:提供重置配置的功能,以便在需要時(如密鑰輪換)重新加載配置。

為了更直觀地理解上述流程,下面用一個流程圖表示:

四、實戰(zhàn)

1. 配置結(jié)構(gòu)定義

// JWT核心配置
type JWTConfig struct {
    Secret         []byte              // 加密密鑰 - 建議使用32字節(jié)安全隨機數(shù)
    Issuer         string              // 簽發(fā)者 - 通常為服務(wù)名稱
    SigningMethod  jwt.SigningMethod    // 簽名算法 - 支持HS256/HS384/HS512
    ExpirationTime time.Duration       // 有效時長 - 如24h, 15m等
}

// 自定義Claims結(jié)構(gòu)
type CustomClaims struct {
    UserID   int    `json:"userID"`   // 用戶ID
    UserName string `json:"userName"` // 用戶名
    jwt.RegisteredClaims              // JWT標準字段
}

// 全局配置實例(線程安全)
var (
    jwtConfig *JWTConfig
    mutex     sync.Mutex
)

2. JWT 中間件實現(xiàn)

func JwtMiddleware() gin.HandlerFunc {
    // 定義排除路徑(支持通配符)
    excludedPaths := map[string]bool{
        "/api/v1/login":  true,
        "/public/*":      true,
        "/healthcheck":   true,
    }

    return func(c *gin.Context) {
        // 檢查當前路徑是否在排除列表中
        for path := range excludedPaths {
            if match, _ := filepath.Match(path, c.Request.URL.Path); match {
                c.Next() // 放行請求
                return
            }
        }

        // 獲取Authorization頭
        authHeader := c.GetHeader("Authorization")
        if authHeader == "" {
            c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
                "code":    40101,
                "message": "Authorization header is required",
            })
            return
        }

        // 解析Bearer Token
        tokenString, err := parseBearerToken(authHeader)
        if err != nil {
            c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
                "code":    40102,
                "message": "Invalid token format",
            })
            return
        }

        // 驗證Token
        claims, err := validateJWT(tokenString)
        if err != nil {
            handleJWTError(c, err) // 處理各類驗證錯誤
            return
        }

        // 存儲claims到上下文(后續(xù)路由可通過c.Get("jwt_claims")獲?。?
        c.Set("jwt_claims", claims)
        c.Next()
    }
}

3. Token 生成

// 登錄成功時調(diào)用
func GenerateToken(userID int, userName string) (string, error) {
    conf := config.LoadConfig() // 加載應(yīng)用配置
    jwtConf, err := loadJwtConfig(conf)
    if err != nil {
        return "", fmt.Errorf("failed to load JWT config: %w", err)
    }

    // 創(chuàng)建Claims對象
    claims := CustomClaims{
        UserID:   userID,
        UserName: userName,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(jwtConf.ExpirationTime)),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
            Issuer:    jwtConf.Issuer,
            // 可添加更多聲明如:Subject, Audience等
        },
    }

    // 創(chuàng)建并簽名Token
    token := jwt.NewWithClaims(jwtConf.SigningMethod, claims)
    tokenString, err := token.SignedString(jwtConf.Secret)
    if err != nil {
        return "", fmt.Errorf("failed to sign token: %w", err)
    }

    // 可選:當Redis啟用時存儲Token
    if conf.Redis.Enable {
        redisCli := redis.GetRedisCli() // 獲取Redis連接
        defer redisCli.Close()

        // 序列化Claims
        claimsJSON, err := json.Marshal(claims)
        if err != nil {
            log.Printf("Failed to marshal claims: %v", err)
            // 不阻斷流程,僅記錄錯誤
        } else {
            // 存儲到Redis,使用Token作為Key
            err = redisCli.Set(context.Background(), tokenString, claimsJSON, jwtConf.ExpirationTime).Err()
            if err != nil {
                log.Printf("Redis set error: %v", err)
            }
        }
    }
    
    return tokenString, nil
}

4. Token 驗證邏輯

func validateJWT(tokenString string) (*CustomClaims, error) {
    conf := config.LoadConfig()
    // 優(yōu)先從Redis獲?。ㄈ绻麊⒂茫?
    if conf.Redis.Enable {
        redisCli := redis.GetRedisCli()
        defer redisCli.Close()

        // 嘗試從Redis獲取
        val, err := redisCli.Get(context.Background(), tokenString).Result()
        if err == nil {
            var claims CustomClaims
            if err := json.Unmarshal([]byte(val), &claims); err == nil {
                // 檢查過期時間
                if claims.ExpiresAt != nil && claims.ExpiresAt.Before(time.Now()) {
                    return nil, jwt.ErrTokenExpired
                }
                return &claims, nil
            }
        }
        // Redis查找失敗不影響后續(xù)流程
    }

    // JWT庫驗證
    token, err := jwt.ParseWithClaims(
        tokenString, 
        &CustomClaims{}, 
        func(token *jwt.Token) (interface{}, error) {
            // 驗證簽名算法是否匹配
            if token.Method != jwtConfig.SigningMethod {
                return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
            }
            return jwtConfig.Secret, nil
        },
    )

    if err != nil {
        return nil, err
    }

    // 驗證Claims結(jié)構(gòu)
    if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
        return claims, nil
    }
    
    return nil, jwt.ErrTokenInvalidClaims
}

5. 錯誤處理機制

func handleJWTError(c *gin.Context, err error) {
    var errorResponse gin.H
    
    switch {
    case errors.Is(err, jwt.ErrTokenExpired):
        errorResponse = gin.H{
            "code":    40103,
            "message": "Token expired",
            "action":  "refresh_token",
        }
    case errors.Is(err, jwt.ErrTokenMalformed):
        errorResponse = gin.H{
            "code":    40104,
            "message": "Malformed token",
        }
    case errors.Is(err, jwt.ErrTokenSignatureInvalid):
        errorResponse = gin.H{
            "code":    40105,
            "message": "Invalid signature",
        }
    default:
        errorResponse = gin.H{
            "code":    40100,
            "message": "Authentication failed",
        }
    }
    
    c.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse)
}

6. 輔助函數(shù)實現(xiàn)

// Bearer Token解析
func parseBearerToken(header string) (string, error) {
    const bearerPrefix = "Bearer "
    if len(header) <= len(bearerPrefix) || !strings.HasPrefix(header, bearerPrefix) {
        return "", fmt.Errorf("authorization header format must be 'Bearer {token}'")
    }
    return strings.TrimSpace(header[len(bearerPrefix):]), nil
}

// 配置加載與初始化
func loadJwtConfig(conf *config.Config) (*JWTConfig, error) {
    mutex.Lock()
    defer mutex.Unlock()

    // 如果已初始化,直接返回
    if jwtConfig != nil {
        return jwtConfig, nil
    }

    // 生成32字節(jié)安全密鑰
    b := make([]byte, 32)
    if _, err := rand.Read(b); err != nil {
        fmt.Println("failed to generate secure secret")
    }
    secret := base64.URLEncoding.EncodeToString(b)

    // 解析簽名算法
    signingMethod := jwt.GetSigningMethod(conf.Jwt.SigningMethod)
    if signingMethod == nil {
        return nil, fmt.Errorf("invalid signing method")
    }

    // 解析過期時間
    expirationTime, err := time.ParseDuration(conf.Jwt.ExpirationTime)
    if err != nil {
        return nil, fmt.Errorf("invalid expiration format: %w", err)
    }

    // 創(chuàng)建配置實例
    jwtConfig = &JWTConfig{
        Secret:         secret,
        Issuer:         conf.Jwt.Issuer,
        SigningMethod:  signingMethod,
        ExpirationTime: expirationTime,
    }
    
    return jwtConfig, nil
}

// 重置配置(用于密鑰輪換)
func ResetJWTConfig() {
    mutex.Lock()
    defer mutex.Unlock()
    jwtConfig = nil
}

7. config 配置文件

# config.yaml 示例

# redis配置
redis:
  enable: false                     # 是否啟用 redis
  addr: localhost:6379
  password:
  db: 0

# jwt配置
jwt:
  issuer: vespeng                   # 簽發(fā)者
  signingMethod: HS256              # 簽名算法 (HS256、HS384、HS512)
  expirationTime: 30m               # 過期時間 (單位 min)

具體加載配置文件可參考 Go 搭建高效的 Gin Web 目錄結(jié)構(gòu)

五、在 Gin 路由中使用

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

    // 應(yīng)用全局JWT中間件
    r.Use(JwtMiddleware())

    // 登錄路由(排除中間件)
    r.POST("/login", func(c *gin.Context) {
        // 1. 驗證用戶憑證(省略具體實現(xiàn))
        user := authenticate(c.PostForm("username"), c.PostForm("password"))
        
        if user == nil {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
            return
        }
        
        // 2. 生成JWT
        token, err := GenerateToken(user.ID, user.Name)
        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": "Token generation failed"})
            return
        }
        
        // 3. 返回響應(yīng)
        c.JSON(http.StatusOK, gin.H{
            "token": token,
            "expires_in": int(jwtConfig.ExpirationTime.Seconds()),
        })
    })

    // 需要認證的路由
    authGroup := r.Group("/api")
    {
        authGroup.GET("/business", func(c *gin.Context) {
            // 從上下文獲取claims
            rawClaims, exists := c.Get("jwt_claims")
            if !exists {
                c.JSON(http.StatusUnauthorized, gin.H{"error": "Claims not found"})
                return
            }
            
            // 類型斷言
            claims, ok := rawClaims.(*CustomClaims)
            if !ok {
                c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid claims format"})
                return
            }
            
            // 返回用戶信息
            c.JSON(http.StatusOK, gin.H{
                "user_id": claims.UserID,
                "username": claims.UserName,
            })
        })
        
        // 其他需要認證的路由...
    }

    // 啟動服務(wù)
    r.Run(":8080")
}

以上實現(xiàn)方案既保證了安全性,又能保持代碼的整潔和可維護性。根據(jù)實際業(yè)務(wù)需求,你可以靈活調(diào)整過期時間、簽名算法等參數(shù),以滿足不同場景需求。

到此這篇關(guān)于Go實現(xiàn)JWT認證中間件的項目實戰(zhàn)的文章就介紹到這了,更多相關(guān)Go JWT認證中間件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go語言配置解析庫viper的使用指南

    Go語言配置解析庫viper的使用指南

    viper?配置管理解析庫,是由大神?Steve?Francia?開發(fā),本文就來和大家詳細講講它的具體使用,文中的示例代碼講解詳細,需要的可以收藏一下
    2023-06-06
  • Go?處理大數(shù)組使用?for?range?和?for?循環(huán)的區(qū)別

    Go?處理大數(shù)組使用?for?range?和?for?循環(huán)的區(qū)別

    這篇文章主要介紹了Go處理大數(shù)組使用for?range和for循環(huán)的區(qū)別,對于遍歷大數(shù)組而言,for循環(huán)能比for?range循環(huán)更高效與穩(wěn)定,這一點在數(shù)組元素為結(jié)構(gòu)體類型更加明顯,下文具體分析感興趣得小伙伴可以參考一下
    2022-05-05
  • 在go文件服務(wù)器加入http.StripPrefix的用途介紹

    在go文件服務(wù)器加入http.StripPrefix的用途介紹

    這篇文章主要介紹了在go文件服務(wù)器加入http.StripPrefix的用途介紹,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Golang開發(fā)Go依賴管理工具dep安裝驗證實現(xiàn)過程

    Golang開發(fā)Go依賴管理工具dep安裝驗證實現(xiàn)過程

    這篇文章主要為大家介紹了Golang開發(fā)Go依賴管理工具dep安裝驗證及初始化一系列實現(xiàn)過程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步
    2021-11-11
  • 五步讓你成為GO 語言高手

    五步讓你成為GO 語言高手

    本文給大家介紹的這里是GO程序員的五個進化階段,從最開始的菜逼到最終的布道者,附上各種示例,一步步走向大神之路,推薦給小伙伴們,有需要的朋友可以參考下
    2015-03-03
  • 詳解如何在Go語言中生成隨機種子

    詳解如何在Go語言中生成隨機種子

    這篇文章主要為大家詳細介紹了如何在Go語言中生成隨機種子,文中的示例代碼講解詳細,具有一定的借鑒價值,有需要的小伙伴可以參考一下
    2024-04-04
  • golang敏感詞過濾的實現(xiàn)

    golang敏感詞過濾的實現(xiàn)

    本文主要介紹了golang敏感詞過濾的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-01-01
  • Go Gin實現(xiàn)文件上傳下載的示例代碼

    Go Gin實現(xiàn)文件上傳下載的示例代碼

    這篇文章主要介紹了Go Gin實現(xiàn)文件上傳下載的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-04-04
  • golang有用的庫及工具 之 zap.Logger包的使用指南

    golang有用的庫及工具 之 zap.Logger包的使用指南

    這篇文章主要介紹了golang有用的庫及工具 之 zap.Logger包的使用指南,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Gin框架中參數(shù)校驗優(yōu)化詳解

    Gin框架中參數(shù)校驗優(yōu)化詳解

    這篇文章主要為大家詳細介紹了Gin框架中參數(shù)校驗優(yōu)化的相關(guān)知識,文中的示例代碼講解詳細,具有一定的學習價值,感興趣的小伙伴可以了解下
    2023-08-08

最新評論