Go語(yǔ)言中雙Token登錄系統(tǒng)的思路與實(shí)現(xiàn)詳解
引言
在現(xiàn)代Web應(yīng)用中,身份認(rèn)證是保障系統(tǒng)安全的重要環(huán)節(jié)。傳統(tǒng)的單Token認(rèn)證方式存在一些安全隱患,如Token泄露可能導(dǎo)致長(zhǎng)期風(fēng)險(xiǎn)。雙Token機(jī)制(Access Token + Refresh Token)提供了更好的安全性和用戶體驗(yàn)。本文將介紹如何使用Go語(yǔ)言實(shí)現(xiàn)雙Token登錄系統(tǒng)。
雙Token機(jī)制概述
雙Token機(jī)制包含兩種令牌:
- Access Token:短期有效的令牌,用于訪問(wèn)受保護(hù)資源
- Refresh Token:長(zhǎng)期有效的令牌,用于獲取新的Access Token
這種機(jī)制的優(yōu)勢(shì)在于:
- Access Token有效期短,即使泄露影響有限
- Refresh Token不直接用于資源訪問(wèn),降低了泄露風(fēng)險(xiǎn)
- 無(wú)需頻繁重新登錄,保持用戶體驗(yàn)
實(shí)現(xiàn)思路
1. 數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)
首先定義Token相關(guān)的數(shù)據(jù)結(jié)構(gòu):
type TokenDetails struct { AccessToken string RefreshToken string AccessUuid string RefreshUuid string AtExpires int64 RtExpires int64 } type AccessDetails struct { AccessUuid string UserId uint64 }
2. Token生成與存儲(chǔ)
使用JWT(JSON Web Token)生成Token,并存儲(chǔ)在Redis中:
func CreateToken(userid uint64) (*TokenDetails, error) { td := &TokenDetails{} td.AtExpires = time.Now().Add(time.Minute * 15).Unix() td.AccessUuid = uuid.New().String() td.RtExpires = time.Now().Add(time.Hour * 24 * 7).Unix() td.RefreshUuid = uuid.New().String() // 創(chuàng)建Access Token atClaims := jwt.MapClaims{} atClaims["authorized"] = true atClaims["access_uuid"] = td.AccessUuid atClaims["user_id"] = userid atClaims["exp"] = td.AtExpires at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims) td.AccessToken, _ = at.SignedString([]byte(os.Getenv("ACCESS_SECRET"))) // 創(chuàng)建Refresh Token rtClaims := jwt.MapClaims{} rtClaims["refresh_uuid"] = td.RefreshUuid rtClaims["user_id"] = userid rtClaims["exp"] = td.RtExpires rt := jwt.NewWithClaims(jwt.SigningMethodHS256, rtClaims) td.RefreshToken, _ = rt.SignedString([]byte(os.Getenv("REFRESH_SECRET"))) return td, nil } func CreateAuth(userid uint64, td *TokenDetails) error { at := time.Unix(td.AtExpires, 0) rt := time.Unix(td.RtExpires, 0) now := time.Now() // 存儲(chǔ)Access Token errAccess := client.Set(td.AccessUuid, strconv.Itoa(int(userid)), at.Sub(now)).Err() if errAccess != nil { return errAccess } // 存儲(chǔ)Refresh Token errRefresh := client.Set(td.RefreshUuid, strconv.Itoa(int(userid)), rt.Sub(now)).Err() if errRefresh != nil { return errRefresh } return nil }
3. 登錄接口實(shí)現(xiàn)
func Login(c *gin.Context) { var user User if err := c.ShouldBindJSON(&user); err != nil { c.JSON(http.StatusUnprocessableEntity, "Invalid json provided") return } // 驗(yàn)證用戶憑據(jù) // ... // 生成Token td, err := CreateToken(user.ID) if err != nil { c.JSON(http.StatusUnprocessableEntity, err.Error()) return } // 存儲(chǔ)Token saveErr := CreateAuth(user.ID, td) if saveErr != nil { c.JSON(http.StatusUnprocessableEntity, saveErr.Error()) return } tokens := map[string]string{ "access_token": td.AccessToken, "refresh_token": td.RefreshToken, } c.JSON(http.StatusOK, tokens) }
4. Token刷新機(jī)制
func Refresh(c *gin.Context) { mapToken := map[string]string{} if err := c.ShouldBindJSON(&mapToken); err != nil { c.JSON(http.StatusUnprocessableEntity, err.Error()) return } refreshToken := mapToken["refresh_token"] // 驗(yàn)證Refresh Token token, err := jwt.Parse(refreshToken, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return []byte(os.Getenv("REFRESH_SECRET")), nil }) if err != nil { c.JSON(http.StatusUnauthorized, "Refresh token expired") return } // 檢查Token是否有效 if _, ok := token.Claims.(jwt.Claims); !ok && !token.Valid { c.JSON(http.StatusUnauthorized, err) return } // 提取claims claims, ok := token.Claims.(jwt.MapClaims) if ok && token.Valid { refreshUuid, ok := claims["refresh_uuid"].(string) if !ok { c.JSON(http.StatusUnprocessableEntity, err) return } userId, err := strconv.ParseUint(fmt.Sprintf("%.f", claims["user_id"]), 10, 64) if err != nil { c.JSON(http.StatusUnprocessableEntity, "Error occurred") return } // 刪除舊的Refresh Token deleted, delErr := DeleteAuth(refreshUuid) if delErr != nil || deleted == 0 { c.JSON(http.StatusUnauthorized, "unauthorized") return } // 創(chuàng)建新的Token對(duì) ts, createErr := CreateToken(userId) if createErr != nil { c.JSON(http.StatusForbidden, createErr.Error()) return } // 保存新的Token saveErr := CreateAuth(userId, ts) if saveErr != nil { c.JSON(http.StatusForbidden, saveErr.Error()) return } tokens := map[string]string{ "access_token": ts.AccessToken, "refresh_token": ts.RefreshToken, } c.JSON(http.StatusCreated, tokens) } else { c.JSON(http.StatusUnauthorized, "refresh expired") } }
5. 中間件實(shí)現(xiàn)Token驗(yàn)證
func TokenAuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { err := TokenValid(c.Request) if err != nil { c.JSON(http.StatusUnauthorized, err.Error()) c.Abort() return } c.Next() } } func TokenValid(r *http.Request) error { token, err := VerifyToken(r) if err != nil { return err } if _, ok := token.Claims.(jwt.Claims); !ok && !token.Valid { return err } return nil } func VerifyToken(r *http.Request) (*jwt.Token, error) { tokenString := ExtractToken(r) token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return []byte(os.Getenv("ACCESS_SECRET")), nil }) if err != nil { return nil, err } return token, nil } func ExtractToken(r *http.Request) string { bearToken := r.Header.Get("Authorization") strArr := strings.Split(bearToken, " ") if len(strArr) == 2 { return strArr[1] } return "" }
完整流程
- 用戶登錄:提供用戶名密碼,服務(wù)端驗(yàn)證后返回Access Token和Refresh Token
- 訪問(wèn)受保護(hù)資源:客戶端在請(qǐng)求頭中攜帶Access Token
- Access Token過(guò)期:服務(wù)端返回401錯(cuò)誤
- 刷新Token:客戶端使用Refresh Token請(qǐng)求新的Token對(duì)
- 繼續(xù)訪問(wèn):使用新的Access Token訪問(wèn)資源
總結(jié)
通過(guò)Go語(yǔ)言實(shí)現(xiàn)雙Token認(rèn)證機(jī)制,我們能夠構(gòu)建更安全的身份認(rèn)證系統(tǒng)。這種機(jī)制在保證安全性的同時(shí),也提供了良好的用戶體驗(yàn)。實(shí)際應(yīng)用中,可以根據(jù)業(yè)務(wù)需求調(diào)整Token的有效期和實(shí)現(xiàn)細(xì)節(jié)。
以上就是Go語(yǔ)言中雙Token登錄系統(tǒng)的思路與實(shí)現(xiàn)詳解的詳細(xì)內(nèi)容,更多關(guān)于Go雙Token登錄的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
GO語(yǔ)言基礎(chǔ)庫(kù)os包的函數(shù)全面解析
這篇文章主要為大家介紹了GO語(yǔ)言基礎(chǔ)庫(kù)os包的函數(shù)全面解析, 有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12Go?CSV包實(shí)現(xiàn)結(jié)構(gòu)體和csv內(nèi)容互轉(zhuǎn)工具詳解
這篇文章主要介紹了Go?CSV包實(shí)現(xiàn)結(jié)構(gòu)體和csv內(nèi)容互轉(zhuǎn)工具詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03詳解Go程序添加遠(yuǎn)程調(diào)用tcpdump功能
這篇文章主要介紹了go程序添加遠(yuǎn)程調(diào)用tcpdump功能,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05