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

作者:

文章目录
  • 在现代Web应用中,身份认证是保障系统安全的重要环节。传统的单Token认证方式存在一些安全隐患,如Token泄露可能导致长期风险。双Token机制(Access Token + Refresh Token)提供了更好的安全性和用户体验。本文将介绍如何使用Go语言实现双Token登录系统。
  • 双Token机制包含两种令牌: Access Token:短期有效的令牌,用于访问受保护资源 Refresh Token:长期有效的令牌,用于获取新的Access Token 这种机制的优势在于: Access Token有效期短,即使泄露影响有限 Refresh Token不直接用于资源访问,降低了泄露风险 无需频繁重新登录,保持用户体验
  • 用户登录:提供用户名密码,服务端验证后返回Access Token和Refresh Token 访问受保护资源:客户端在请求头中携带Access Token Access Token过期:服务端返回401错误 刷新Token:客户端使用Refresh Token请求新的Token对 继续访问:使用新的Access Token访问资源
  • 通过Go语言实现双Token认证机制,我们能够构建更安全的身份认证系统。这种机制在保证安全性的同时,也提供了良好的用户体验。实际应用中,可以根据业务需求调整Token的有效期和实现细节。 以上就是Go语言中双Token登录系统的思路与实现详解的详细内容,更多关于Go双Token登录的资料请关注风君子博客其它相关文章! 您可能感兴趣的文章: Golang使用Token来验证 Go 实现基于Token 的登录流程深度分析 Golang token的生成和解析详解 go gin+token(JWT)验证实现登陆验证 golang jwt+token验证的实现
  • 目录
    • 引言
    • 双Token机制概述
    • 实现思路
      • 1. 数据结构设计
      • 2. Token生成与存储
      • 3. 登录接口实现
      • 4. Token刷新机制
      • 5. 中间件实现Token验证
    • 完整流程
      • 总结

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

        双Token机制包含两种令牌:

        • Access Token:短期有效的令牌,用于访问受保护资源
        • Refresh Token:长期有效的令牌,用于获取新的Access Token

        这种机制的优势在于:

        • Access Token有效期短,即使泄露影响有限
        • Refresh Token不直接用于资源访问,降低了泄露风险
        • 无需频繁重新登录,保持用户体验

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

        type TokenDetails struct {
            AccessToken  string
            RefreshToken string
            AccessUuid   string
            RefreshUuid  string
            AtExpires    int64
            RtExpires    int64
        }
        
        type AccessDetails struct {
            AccessUuid string
            UserId     uint64
        }
        

        使用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
        }
        

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

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

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

        • 用户登录:提供用户名密码,服务端验证后返回Access Token和Refresh Token
        • 访问受保护资源:客户端在请求头中携带Access Token
        • Access Token过期:服务端返回401错误
        • 刷新Token:客户端使用Refresh Token请求新的Token对
        • 继续访问:使用新的Access Token访问资源

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

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

        您可能感兴趣的文章:

        • Golang使用Token来验证
        • Go 实现基于Token 的登录流程深度分析
        • Golang token的生成和解析详解
        • go gin+token(JWT)验证实现登陆验证
        • golang jwt+token验证的实现

        站内搜索