Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Go双Token登录

Go语言中双Token登录系统的思路与实现详解

作者:Go Dgg

在现代Web应用中,身份认证是保障系统安全的重要环节,本文将介绍如何使用Go语言实现双Token登录系统,文中的示例代码讲解详细,需要的可以了解下

引言

在现代Web应用中,身份认证是保障系统安全的重要环节。传统的单Token认证方式存在一些安全隐患,如Token泄露可能导致长期风险。双Token机制(Access Token + Refresh Token)提供了更好的安全性和用户体验。本文将介绍如何使用Go语言实现双Token登录系统。

双Token机制概述

双Token机制包含两种令牌:

这种机制的优势在于:

实现思路

1. 数据结构设计

首先定义Token相关的数据结构:

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()
    
    // 创建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")))
    
    // 创建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. 登录接口实现

func Login(c *gin.Context) {
    var user User
    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(http.StatusUnprocessableEntity, "Invalid json provided")
        return
    }
    
    // 验证用户凭据
    // ...
    
    // 生成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刷新机制

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"]
    
    // 验证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
        }
        
        // 创建新的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. 中间件实现Token验证

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 ""
}

完整流程

总结

通过Go语言实现双Token认证机制,我们能够构建更安全的身份认证系统。这种机制在保证安全性的同时,也提供了良好的用户体验。实际应用中,可以根据业务需求调整Token的有效期和实现细节。

以上就是Go语言中双Token登录系统的思路与实现详解的详细内容,更多关于Go双Token登录的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文