不能直接复用gin的session中间件做统一登录,因其默认内存存储导致多实例session不互通,且cookie在跨域、子域、https等场景易失效;jwt+redis方案通过jti存redis实现无状态认证、主动登出与吊销。

为什么不能直接复用 Gin 的 session 中间件做统一登录
因为 Gin 本身不带 session 存储实现,官方推荐的 gin-contrib/sessions 默认用内存存储(cookie 或 memory store),在多实例部署时各节点 session 数据不互通,用户登录后可能在另一个节点被判定为未登录。更严重的是,它依赖 http.SetCookie 写入客户端,而跨域、子域、HTTPS 等场景下 cookie 的 Domain 和 Secure 属性稍有偏差就会失效——你看到的“有时能登、有时 401”,大概率是这个原因。
JWT + Redis 实现无感登录的关键点
真正可行的方案是:登录成功后生成 JWT(含用户 ID、过期时间、随机 jti),把 jti 存进 Redis 并设 TTL(和 token 过期时间一致);后续所有请求由中间件校验 token 签名 + 查询 Redis 是否存在该 jti。这样既避免 session 共享难题,又支持主动登出和 token 吊销。
-
jwt.ParseWithClaims必须配合自定义MyClaims结构体,且必须嵌入jwt.RegisteredClaims,否则无法正确解析ExpiresAt - Redis key 建议用
"jwt:" + jti格式,value 可为空(只用作存在性判断),避免序列化开销 - 中间件里别直接用
c.Request.Header.Get("Authorization"),要按 RFC 7235 解析:strings.TrimPrefix(authHeader, "Bearer "),否则前端漏写Bearer就静默失败 - token 过期时间建议设为 24h,但 Redis TTL 要额外加 5 分钟缓冲(防止刚过期时 Redis 还没清理完,导致误判)
如何让多个子域名应用共享登录态
关键不在 Gin 代码,而在 HTTP 响应头和前端配置。后端返回的 token 必须通过 Set-Cookie 写入根域(如 .example.com),且 Domain 参数不能硬编码,得根据请求 Host 动态推导:
Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。
- 用
c.Request.Host提取域名,再用strings.Split截取一级父域(如app1.example.com→example.com) http.SetCookie(c.Writer, &http.Cookie{ Name: "auth_token", Value: tokenString, Domain: parentDomain, Path: "/", HttpOnly: true, Secure: true, MaxAge: 24*3600 })- 前端 axios/fetch 请求必须开启
withCredentials: true,否则浏览器不带 cookie - 如果用 JWT 放在 Authorization header,就不用管 cookie,但前端每次都要手动取 localStorage 的 token 并拼接,容易遗漏
登出时 Redis 删除不生效的常见原因
不是代码没写 DEL,而是并发或网络延迟导致删除滞后。真实线上环境必须加两层防护:
- 登出接口里先执行
redis.Del(ctx, "jwt:"+jti),再立即调用redis.Exists(ctx, "jwt:"+jti)验证是否真删了,失败则重试一次 - JWT 中间件里增加「软校验」:若 Redis 返回不存在,但 token 未过期,先记录日志并放行(避免雪崩),同时触发异步刷新 token 流程
- 千万别用
redis.FlushDB()清库——这会干掉所有用户的登录态
最易被忽略的是:JWT 的 jti 字段必须全局唯一且不可预测,别用 time.Now().Unix(),要用 uuid.NewString() 或 rand.Intn() 加盐生成,否则撞 jti 会导致误吊销。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










