当前位置: 首页 > article >正文

Go 后端中双 token 的实现模板

下面是一个典型的 Go 后端双 Token 认证机制 实现模板,使用 Gin 框架 + JWT + Redis,结构清晰、可拓展,适合实战开发。


项目结构建议

/utils├── jwt.go         // Access & Refresh token 的生成和解析├── claims.go      // 从请求中提取用户信息
/middleware└── auth.go        // 中间件:校验 Access Token + 黑名单
/controller├── auth.go        // 登录、刷新、登出接口
/redis└── client.go      // Redis 黑名单管理

1. JWT 工具(utils/jwt.go

package utilsimport ("time""github.com/golang-jwt/jwt/v5"
)var accessSecret = []byte("access_secret")
var refreshSecret = []byte("refresh_secret")type CustomClaims struct {UserID uint `json:"user_id"`jwt.RegisteredClaims
}func GenerateAccessToken(userID uint) (string, error) {claims := CustomClaims{UserID: userID,RegisteredClaims: jwt.RegisteredClaims{ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),IssuedAt:  jwt.NewNumericDate(time.Now()),},}return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(accessSecret)
}func GenerateRefreshToken(userID uint) (string, error) {claims := CustomClaims{UserID: userID,RegisteredClaims: jwt.RegisteredClaims{ExpiresAt: jwt.NewNumericDate(time.Now().Add(7 * 24 * time.Hour)),IssuedAt:  jwt.NewNumericDate(time.Now()),},}return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(refreshSecret)
}func ParseToken(tokenStr string, isRefresh bool) (*CustomClaims, error) {key := accessSecretif isRefresh {key = refreshSecret}token, err := jwt.ParseWithClaims(tokenStr, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {return key, nil})if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {return claims, nil}return nil, err
}

2. 中间件验证 Access Token(middleware/auth.go

package middlewareimport ("chat/redis""chat/utils""github.com/gin-gonic/gin""net/http""strings"
)func JWTAuthMiddleware() gin.HandlerFunc {return func(c *gin.Context) {authHeader := c.GetHeader("Authorization")if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {c.JSON(http.StatusUnauthorized, gin.H{"error": "token required"})c.Abort()return}token := strings.TrimPrefix(authHeader, "Bearer ")claims, err := utils.ParseToken(token, false)if err != nil {c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})c.Abort()return}if redis.IsBlacklisted(token) {c.JSON(http.StatusUnauthorized, gin.H{"error": "token is blacklisted"})c.Abort()return}c.Set("userID", claims.UserID)c.Next()}
}

3. Redis 黑名单管理(redis/client.go

package redisimport ("context""time""github.com/redis/go-redis/v9"
)var RDB *redis.Clientfunc InitRedis() {RDB = redis.NewClient(&redis.Options{Addr: "localhost:6379",DB:   0,})
}var ctx = context.Background()func AddToBlacklist(token string, expiration time.Duration) {RDB.Set(ctx, "blacklist:"+token, "1", expiration)
}func IsBlacklisted(token string) bool {val, err := RDB.Get(ctx, "blacklist:"+token).Result()return err == nil && val == "1"
}

4. 登录、刷新、退出接口(controller/auth.go

package controllerimport ("chat/redis""chat/utils""github.com/gin-gonic/gin""net/http""time"
)func Login(c *gin.Context) {// 假设账号密码校验通过,用户ID是 123userID := uint(123)accessToken, _ := utils.GenerateAccessToken(userID)refreshToken, _ := utils.GenerateRefreshToken(userID)c.SetCookie("refresh_token", refreshToken, 7*24*3600, "/", "localhost", false, true)c.JSON(http.StatusOK, gin.H{"access_token": accessToken})
}func Refresh(c *gin.Context) {refreshToken, err := c.Cookie("refresh_token")if err != nil {c.JSON(http.StatusUnauthorized, gin.H{"error": "refresh token not found"})return}claims, err := utils.ParseToken(refreshToken, true)if err != nil {c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid refresh token"})return}newAccessToken, _ := utils.GenerateAccessToken(claims.UserID)c.JSON(http.StatusOK, gin.H{"access_token": newAccessToken})
}func Logout(c *gin.Context) {token := c.GetHeader("Authorization")if token != "" {token = token[len("Bearer "):]redis.AddToBlacklist(token, 15*time.Minute) // 过期时间与 access token 一致}c.SetCookie("refresh_token", "", -1, "/", "localhost", false, true)c.JSON(http.StatusOK, gin.H{"message": "logout success"})
}

5. 路由注册(main.go

r := gin.Default()
redis.InitRedis()auth := r.Group("/auth")
{auth.POST("/login", controller.Login)auth.POST("/refresh", controller.Refresh)auth.POST("/logout", controller.Logout)
}api := r.Group("/api", middleware.JWTAuthMiddleware())
{api.GET("/me", func(c *gin.Context) {userID := c.GetUint("userID")c.JSON(http.StatusOK, gin.H{"user_id": userID})})
}

http://www.lryc.cn/news/2379656.html

相关文章:

  • 【拥抱AI】Deer-Flow字节跳动开源的多智能体深度研究框架
  • 第六天——贪心算法——字符串分隔
  • Python 条件语句详解
  • 前端获取用户的公网 IP 地址
  • 在Maven中替换文件内容的插件和方法
  • 符合Python风格的对象(再谈向量类)
  • 云电竞服务器 工作原理
  • 【数据结构】线性表--队列
  • [Vue3]语法变动
  • Ubuntu服务器开启SNMP服务 监控系统配置指南 -优雅草星云智控简易化操作
  • linux本地部署ollama+deepseek过程
  • 从零开始实现大语言模型(十五):并行计算与分布式机器学习
  • OpenCV进阶操作:指纹验证、识别
  • 网络安全-等级保护(等保) 2-5 GB/T 25070—2019《信息安全技术 网络安全等级保护安全设计技术要求》-2019-05-10发布【现行】
  • 3D生成新突破:阶跃星辰Step1X-3D开源,可控性大幅提升
  • MySQL数据类型之VARCHAR和CHAR使用详解
  • 数字人 LAM 部署笔记
  • 《Docker 入门与进阶:架构剖析、隔离原理及安装实操》
  • 基于Akamai云计算平台的OTT媒体点播转码解决方案
  • 【MySQL】02.数据库基础
  • 选错方向太致命,华为HCIE数通和云计算到底怎么选?
  • 经典启发算法【早期/启发式/HC爬山/SA模拟退火/TS禁忌搜/IA免疫 思想流程举例全】
  • IntraWeb 16.0.2 + Bootstrap 4 居中布局实战(附源码+效果图)
  • Spring 框架中适配器模式的五大典型应用场景
  • 【Java ee初阶】jvm(3)
  • C 语言多维数组:定义、初始化与访问的深度解析
  • 浅入ES5、ES6(ES2015)、ES2023(ES14)版本对比,及使用建议---ES6就够用(个人觉得)
  • 23种设计模式考试趋势分析之——适配器(Adapter)设计模式——求三连
  • Python 翻译词典小程序
  • 【Linux笔记】——线程互斥与互斥锁的封装