Go 后端中雙 token 的實現模板

下面是一個典型的 Go 后端雙 Token 認證機制 實現模板,使用 Gin 框架 + JWT + Redis,結構清晰、可拓展,適合實戰開發。


項目結構建議

/utils├── jwt.go         // Access & Refresh token 的生成和解析├── claims.go      // 從請求中提取用戶信息
/middleware└── auth.go        // 中間件:校驗 Access Token + 黑名單
/controller├── auth.go        // 登錄、刷新、登出接口
/redis└── client.go      // Redis 黑名單管理

1. JWT 工具(utils/jwt.go

package utilsimport ("time""github.com/golang-jwt/jwt/v5"
)var accessSecret = []byte("access_secret")
var refreshSecret = []byte("refresh_secret")type CustomClaims struct {UserID uint `json:"user_id"`jwt.RegisteredClaims
}func GenerateAccessToken(userID uint) (string, error) {claims := CustomClaims{UserID: userID,RegisteredClaims: jwt.RegisteredClaims{ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),IssuedAt:  jwt.NewNumericDate(time.Now()),},}return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(accessSecret)
}func GenerateRefreshToken(userID uint) (string, error) {claims := CustomClaims{UserID: userID,RegisteredClaims: jwt.RegisteredClaims{ExpiresAt: jwt.NewNumericDate(time.Now().Add(7 * 24 * time.Hour)),IssuedAt:  jwt.NewNumericDate(time.Now()),},}return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(refreshSecret)
}func ParseToken(tokenStr string, isRefresh bool) (*CustomClaims, error) {key := accessSecretif isRefresh {key = refreshSecret}token, err := jwt.ParseWithClaims(tokenStr, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {return key, nil})if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {return claims, nil}return nil, err
}

2. 中間件驗證 Access Token(middleware/auth.go

package middlewareimport ("chat/redis""chat/utils""github.com/gin-gonic/gin""net/http""strings"
)func JWTAuthMiddleware() gin.HandlerFunc {return func(c *gin.Context) {authHeader := c.GetHeader("Authorization")if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {c.JSON(http.StatusUnauthorized, gin.H{"error": "token required"})c.Abort()return}token := strings.TrimPrefix(authHeader, "Bearer ")claims, err := utils.ParseToken(token, false)if err != nil {c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})c.Abort()return}if redis.IsBlacklisted(token) {c.JSON(http.StatusUnauthorized, gin.H{"error": "token is blacklisted"})c.Abort()return}c.Set("userID", claims.UserID)c.Next()}
}

3. Redis 黑名單管理(redis/client.go

package redisimport ("context""time""github.com/redis/go-redis/v9"
)var RDB *redis.Clientfunc InitRedis() {RDB = redis.NewClient(&redis.Options{Addr: "localhost:6379",DB:   0,})
}var ctx = context.Background()func AddToBlacklist(token string, expiration time.Duration) {RDB.Set(ctx, "blacklist:"+token, "1", expiration)
}func IsBlacklisted(token string) bool {val, err := RDB.Get(ctx, "blacklist:"+token).Result()return err == nil && val == "1"
}

4. 登錄、刷新、退出接口(controller/auth.go

package controllerimport ("chat/redis""chat/utils""github.com/gin-gonic/gin""net/http""time"
)func Login(c *gin.Context) {// 假設賬號密碼校驗通過,用戶ID是 123userID := uint(123)accessToken, _ := utils.GenerateAccessToken(userID)refreshToken, _ := utils.GenerateRefreshToken(userID)c.SetCookie("refresh_token", refreshToken, 7*24*3600, "/", "localhost", false, true)c.JSON(http.StatusOK, gin.H{"access_token": accessToken})
}func Refresh(c *gin.Context) {refreshToken, err := c.Cookie("refresh_token")if err != nil {c.JSON(http.StatusUnauthorized, gin.H{"error": "refresh token not found"})return}claims, err := utils.ParseToken(refreshToken, true)if err != nil {c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid refresh token"})return}newAccessToken, _ := utils.GenerateAccessToken(claims.UserID)c.JSON(http.StatusOK, gin.H{"access_token": newAccessToken})
}func Logout(c *gin.Context) {token := c.GetHeader("Authorization")if token != "" {token = token[len("Bearer "):]redis.AddToBlacklist(token, 15*time.Minute) // 過期時間與 access token 一致}c.SetCookie("refresh_token", "", -1, "/", "localhost", false, true)c.JSON(http.StatusOK, gin.H{"message": "logout success"})
}

5. 路由注冊(main.go

r := gin.Default()
redis.InitRedis()auth := r.Group("/auth")
{auth.POST("/login", controller.Login)auth.POST("/refresh", controller.Refresh)auth.POST("/logout", controller.Logout)
}api := r.Group("/api", middleware.JWTAuthMiddleware())
{api.GET("/me", func(c *gin.Context) {userID := c.GetUint("userID")c.JSON(http.StatusOK, gin.H{"user_id": userID})})
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/83718.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/83718.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/83718.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Typescript學習教程,從入門到精通,TypeScript 對象語法知識點及案例代碼(7)

TypeScript 對象語法知識點及案例代碼 TypeScript 是 JavaScript 的超集,提供了靜態類型檢查和其他增強功能。在 TypeScript 中,對象是面向對象編程(OOP)的基礎。 一、對象概述 在 TypeScript 中,對象是屬性的集合&a…

應用BERT-GCN跨模態情緒分析:貿易緩和與金價波動的AI歸因

本文運用AI量化分析框架,結合市場情緒因子、宏觀經濟指標及技術面信號,對黃金與美元指數的聯動關系進行解析,揭示本輪貴金屬回調的深層驅動因素。 周三,現貨黃金價格單日跌幅達2.1%,盤中觸及3167.94美元/盎司關鍵價位&…

命令行登錄 MySQL 報 Segmentation fault 故障解決

問題描述:對 mysql8.0.35 源碼進行 make,由于一開始因為yum源問題少安裝依賴庫 庫,在鏈接時遇到錯誤 undefined reference to,后來安裝了相關依賴庫,再次 make 成功。于是將 mysqld 啟動,再用 mysql -u roo…

Axure設計數字鄉村可視化大屏:構建鄉村數據全景圖

今天,讓我們一同深入了解由Axure設計的數字鄉村可視化大屏,看看它如何通過精心的布局和多樣化的圖表類型,將鄉村的各類數據以直觀、易懂的方式呈現出來,為鄉村管理者提供有力的數據支持。 原型效果預覽鏈接:Axure數字鄉…

3D個人簡歷網站 4.小島

1.模型素材 在Sketchfab上下載狐貍島模型,然后轉換為素材資源asset,嫌麻煩直接在網盤鏈接下載素材, Fox’s islandshttps://sketchfab.com/3d-models/foxs-islands-163b68e09fcc47618450150be7785907https://gltf.pmnd.rs/ 素材夸克網盤&a…

智能開發工具PhpStorm v2025.1——增強AI輔助編碼功能

PhpStorm是一個輕量級且便捷的PHP IDE,其旨在提高用戶效率,可深刻理解用戶的編碼,提供智能代碼補全,快速導航以及即時錯誤檢查。可隨時幫助用戶對其編碼進行調整,運行單元測試或者提供可視化debug功能。 立即獲取PhpS…

Spark 的運行模式(--master) 和 部署方式(--deploy-mode)

Spark 的 運行模式(--master) 和 部署方式(--deploy-mode),兩者的核心區別在于 資源調度范圍 和 Driver 進程的位置。 一、核心概念對比 維度--master(運行模式)--deploy-mode(部署…

sqli—labs第八關——布爾盲注

一:確定注入類型 按照我們之前的步驟來 輸入 ?id1 and 11-- ?id1 and 12-- 界面正常 第二行界面異常空白 所以注入類型為單引號閉合型 二: 布爾盲注 1.判斷是否使用條件 (1):存在注入但不會直接顯示查詢結果 …

ARP 原理總結

🌐 一、ARP 原理總結 ARP(Address Resolution Protocol)是用于通過 IP 地址解析 MAC 地址的協議,工作在 鏈路層 與 網絡層之間(OSI 模型的第三層與第二層之間)。 🔁 ARP通信過程: …

SpringCloud——EureKa

目錄 1.前言 1.微服務拆分及遠程調用 3.EureKa注冊中心 遠程調用的問題 eureka原理 搭建EureKaServer 服務注冊 服務發現 1.前言 分布式架構:根據業務功能對系統進行拆分,每個業務模塊作為獨立項目開發,稱為服務。 優點: 降…

機頂盒刷機筆記

疑難雜癥解決 hitool線刷網口不通tftp超時--》關閉防火墻cm201-2卡刷所有包提示失敗abort install--》找個卡刷包只刷fastboot分區再卡刷就能通過了(cm201救磚包 (M8273版子)) 刷機工具 海兔燒錄工具HiTool-STB-5.3.12工具,需要…

Linux動靜態庫制作與原理

什么是庫 庫是寫好的現有的,成熟的,可以復用的代碼。現實中每個程序都要依賴很多基礎的底層庫,不可能每個人的代碼都從零開始,因此庫的存在意義非同尋常。 本質上來說庫是一種可執行代碼的二進制形式,可以被操作系統…

如何通過小智AI制作會說話的機器人玩具?

一、硬件準備與組裝 1. 核心硬件選擇 主控芯片:選擇支持無線網絡連接、音頻處理和可編程接口的嵌入式開發板 音頻模塊:配備拾音麥克風與小型揚聲器,確保語音輸入/輸出功能 顯示模塊:選擇適配的交互顯示屏用于可視化反饋 擴展模…

如何控制郵件發送頻率避免打擾用戶

一、用戶行為 監測用戶與郵件的互動數據,如打開率、點擊率下滑或退訂申請增多,可能是發送頻率過高的警示信號。利用郵件營銷平臺的分析工具,識別這些指標的變動趨勢,為調整提供依據。 二、行業特性與受眾差異 不同行業用戶對郵…

定積分的“偶倍奇零”性質及其使用條件

定積分的“偶倍奇零”性質是針對對稱區間上的奇偶函數積分的重要簡化方法。以下是其核心內容和應用要點: ?一、基本性質 ?偶函數(偶倍)? 若 f(x) 在 [?a,a] 上為偶函數(即 f(?x)f(x)),則: …

如何在 Windows 11 或 10 上安裝 Fliqlo 時鐘屏保

了解如何在 Windows 11 或 10 上安裝 Fliqlo,為您的 PC 或筆記本電腦屏幕添加一個翻轉時鐘屏保以顯示時間。 Fliqlo 是一款適用于 Windows 和 macOS 平臺的免費時鐘屏保。它也適用于移動設備,但僅限于 iPhone 和 iPad。Fliqlo 的主要功能是在用戶不活動時在 PC 或筆記本電腦…

【C/C++】C++并發編程:std::async與std::thread深度對比

文章目錄 C并發編程:std::async與std::thread深度對比1 核心設計目的以及區別2 詳細對比分析3 代碼對比示例4 適用場景建議5 總結 C并發編程:std::async與std::thread深度對比 在 C 中,std::async 和 std::thread 都是用于并發編程的工具&am…

Axure疑難雜癥:垂直菜單展開與收回(4大核心問題與專家級解決方案)

親愛的小伙伴,在您瀏覽之前,煩請關注一下,在此深表感謝!如有幫助請訂閱專欄! Axure產品經理精品視頻課已登錄CSDN可點擊學習https://edu.csdn.net/course/detail/40420 課程主題:垂直菜單展開與收回 主要內容:超長菜單實現、展開與收回bug解釋、Axure9版本限制等問題解…

ASIC和FPGA,到底應該選擇哪個?

ASIC和FPGA各有優缺點。 ASIC針對特定需求,具有高性能、低功耗和低成本(在大規模量產時);但設計周期長、成本高、風險大。FPGA則適合快速原型驗證和中小批量應用,開發周期短,靈活性高,適合初創企…

DAY 30 模塊和庫的導入

知識點回顧: 1.導入官方庫的三種手段 2.導入自定義庫/模塊的方式 3.導入庫/模塊的核心邏輯:找到根目錄(python解釋器的目錄和終端的目錄不一致) 作業:自己新建幾個不同路徑文件嘗試下如何導入 import math # 導入…