开发者

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

开发者 https://www.devze.com 2025-07-09 10:54 出处:网络 作者: Go Dgg
目录引言双Token机制概述实现思路1. 数据结构设计2. Token生成与存储3. 登录接口实现4. Token刷新机制5. 中间件实现Token验证完整流程总结引言
目录
  • 引言
  • 双Token机制概述
  • 实现思路
    • 1. 数据结构设计
    • 2. Token生成与存储
    • 3. 登录接口实现
    • 4. Token刷新机制
    • 5. 中间件实现Token验证
  • 完整流程
    • 总结

      引言

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

      双Token机制概述

      双Token机制包含两种令牌:

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

      这种机制的优势在于:

      • Access Token有效期短,即使泄露影响有限
      • Refresh 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"] android= 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()
          i编程f 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.SVXAXgTntatusUnauthorized, 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登录的资料请关注编程客栈(www.devze.com)其它相关文章!

      0

      精彩评论

      暂无评论...
      验证码 换一张
      取 消

      关注公众号