鉴权中间件中通过c.GetHeader("Authorization")获取token并校验:先切分Bearer前缀,再用github.com/golang-jwt/jwt/v5解析,需显式校验exp;校验成功后c.Set("user_id", userID)透传,handler中用c.Get()安全取值;路由组按权限划分,登录等接口不可置于auth group内。

鉴权中间件里怎么获取并校验 token
Gin 的中间件通过 c.Request 拿请求上下文,token 通常从 Authorization 请求头取,格式为 Bearer xxx。直接用 c.GetHeader("Authorization") 取值后需手动切分,别漏掉空格判断——常见错误是没 trim 或硬写 strings.Split(..., " ") 导致下标越界。
校验逻辑建议封装成独立函数,比如 parseToken(tokenString string) (*jwt.Token, error),避免在中间件里混入解析、验证、claims 提取等多层逻辑。用 github.com/golang-jwt/jwt/v5 时注意:默认的 Parse 不校验过期(VerifyExpiresAt),必须显式传入 jwt.WithValidator 或手动调 token.Claims.(jwt.MapClaims)["exp"] 做时间比对。
- 别用
c.MustGet("user_id")代替校验——那是鉴权之后的事 - 如果 token 存在但签名无效,
jwt.Parse返回非 nil 的*Token+ error,此时token.Valid是 false,不能只判 error 是否为 nil - 开发阶段可加
log.Printf("auth failed: %v", err),但上线前删掉或走 structured logger,避免泄露敏感信息
如何把用户信息透传到后续 handler
校验成功后,要用 c.Set("user_id", userID) 或 c.Set("user", user) 把结构体存进上下文,而不是用全局变量或闭包捕获——Gin 的 c 是每个请求独享的,安全。
后续 handler 中通过 userID, ok := c.Get("user_id") 取值,务必检查 ok。常见坑是忘记类型断言:if uid, ok := c.Get("user_id").(uint64),如果中间件没设或设错类型,会 panic。更稳妥写法是先 v, ok := c.Get("user_id"),再 uid, ok := v.(uint64) 分两步。
- 不要在中间件里直接调
c.Abort()后还执行c.Next()—— 这会导致 handler 仍被调用 - 若需返回统一错误格式(如
map[string]string{"error": "unauthorized"}),用c.JSON(401, ...)后立刻c.Abort() - 透传字段名保持小写加下划线(如
"user_role"),和 JSON 序列化习惯一致,减少 handler 里反复转换
路由组怎么配中间件才不漏保护
用 router.Group("/api") 定义私有路由组,然后链式调用 .Use(authMiddleware),比给每个 GET/POST 单独加更可靠。注意:Group 的嵌套顺序影响中间件生效范围——外层 Group 加的中间件,对内层所有路由生效;但内层 Group 单独加的,不会反向作用到外层。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
典型错误是把登录接口(POST /login)也塞进带 auth 的 group 里,结果永远 401。正确做法是让登录、注册等免鉴权接口走根路由或单独 group:
noAuth := router.Group("/auth")
noAuth.POST("/login", loginHandler)
noAuth.POST("/register", registerHandler)
<p>auth := router.Group("/api")
auth.Use(authMiddleware)
auth.GET("/user", getUserHandler)
auth.PUT("/profile", updateProfileHandler)</p>
- 别在
router.Use()全局挂载鉴权中间件——健康检查、Swagger UI 等公开接口会被拦住 - 如果某些接口需要“可选鉴权”(如查公开资料时未登录也能看,登录后返回更多字段),中间件里别直接 abort,而是设
c.Set("user_id", nil),handler 自行判断 - Gin v1.9+ 支持中间件跳过特定路径,可用
skipPathsmap 配置,但不如按 group 划分清晰
JWT 过期刷新和黑名单怎么处理
纯 JWT 本身无状态,服务端无法主动使 token 失效。真要支持登出或强制踢下线,得引入外部存储(Redis)存黑名单或 refresh token。简单场景下,可在中间件里加一层 Redis 查询:redisClient.Get(ctx, "blacklist:"+tokenID).Val(),命中就拒绝。
refresh token 通常比 access token 生命周期长,且只在登录接口返回、每次调用 /refresh 时校验并换新。注意:refresh token 必须 HttpOnly Cookie 传输,不能放 Header,否则 XSS 可窃取;access token 仍走 Authorization Header。
- 不要把 refresh token 和 access token 存同一个字段(如都叫
token)——前端混淆容易用错 - Redis key 建议带过期时间(
SETEX),和 refresh token 有效期一致,避免无限堆积 - 如果用 short-lived access token(如 15 分钟),前端需静默刷新,后端中间件发现即将过期(
exp - time.Now().Unix() ),可在响应 header 里加 <code>X-Refresh-Token提示前端换 token
实际部署时,authMiddleware 的 panic 恢复、日志采样率、JWT 密钥轮换机制,这些才是压测和线上最常暴露的问题点。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










