Go語言中雙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)致長期風(fēng)險。雙Token機(jī)制(Access Token + Refresh Token)提供了更好的安全性和用戶體驗(yàn)。本文將介紹如何使用Go語言實(shí)現(xiàn)雙Token登錄系統(tǒng)。
雙Token機(jī)制概述
雙Token機(jī)制包含兩種令牌:
- Access Token:短期有效的令牌,用于訪問受保護(hù)資源
- Refresh Token:長期有效的令牌,用于獲取新的Access Token
這種機(jī)制的優(yōu)勢在于:
- Access Token有效期短,即使泄露影響有限
- Refresh Token不直接用于資源訪問,降低了泄露風(fēng)險
- 無需頻繁重新登錄,保持用戶體驗(yàn)
實(shí)現(xiàn)思路
1. 數(shù)據(jù)結(jié)構(gòu)設(shè)計
首先定義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生成與存儲
使用JWT(JSON Web Token)生成Token,并存儲在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()
// 存儲Access Token
errAccess := client.Set(td.AccessUuid, strconv.Itoa(int(userid)), at.Sub(now)).Err()
if errAccess != nil {
return errAccess
}
// 存儲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
}
// 存儲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對
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
- 訪問受保護(hù)資源:客戶端在請求頭中攜帶Access Token
- Access Token過期:服務(wù)端返回401錯誤
- 刷新Token:客戶端使用Refresh Token請求新的Token對
- 繼續(xù)訪問:使用新的Access Token訪問資源
總結(jié)
通過Go語言實(shí)現(xiàn)雙Token認(rèn)證機(jī)制,我們能夠構(gòu)建更安全的身份認(rèn)證系統(tǒng)。這種機(jī)制在保證安全性的同時,也提供了良好的用戶體驗(yàn)。實(shí)際應(yīng)用中,可以根據(jù)業(yè)務(wù)需求調(diào)整Token的有效期和實(shí)現(xiàn)細(xì)節(jié)。
以上就是Go語言中雙Token登錄系統(tǒng)的思路與實(shí)現(xiàn)詳解的詳細(xì)內(nèi)容,更多關(guān)于Go雙Token登錄的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go?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ì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-05-05

