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