Gin中间件是按序执行的func(*gin.Context)函数链,遵循洋葱模型:r.Use(mwA,mwB,mwC)使请求路径为mwA→mwB→mwC→handler→mwC→mwB→mwA,认证类中间件必须调c.Abort()终止链,否则导致越权。

Gin中间件不是“插件”,而是一串按序执行的func(*gin.Context)函数,请求不经过c.Next()就卡住,不调c.Abort()就必然走到最终 handler —— 这是绝大多数新手踩坑的根源。
中间件注册顺序决定执行顺序,不是“谁先写谁先跑”
Gin 中间件的执行顺序严格由 Use() 调用顺序决定,且遵循“洋葱模型”:请求进入时从前到后执行每个中间件的前置逻辑,响应返回时从后到前执行后置逻辑。
-
r.Use(mwA, mwB, mwC)→ 请求路径:mwA→mwB→mwC→handler→mwC→mwB→mwA - 路由组上注册:
api.Use(auth)只影响/api/xxx下所有路由,不影响/health等其他路径 - 单个路由可叠加:
r.GET("/admin", auth, adminOnly, handler),注意这里auth和adminOnly是额外传入的中间件,不是Use()注册的 - 错误示范:
r.Use(recovery); r.Use(logger)会导致 recovery 捕不到 logger 内部 panic(因为 logger 在 recovery 外层)——正确顺序应是recovery最外层
自定义登录验证中间件必须用 c.Abort() 终止链
认证类中间件的核心动作不是“返回 JSON”,而是“阻止后续执行”。一旦校验失败却没调 c.Abort(),请求仍会继续流向业务 handler,造成越权访问。
- 正确写法:
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "token missing"}),它内部已含c.Abort() - 常见错误:只写
c.JSON(401, ...)而不调c.Abort(),结果 handler 依然被执行 - token 解析建议用
c.GetHeader("Authorization"),而非c.Query("token"),避免暴露在 URL 日志中 - 用户信息存上下文用
c.Set("user_id", uid),后续 handler 用c.GetInt64("user_id")安全取值(避免类型断言 panic)
c.Next() 不是“继续往下走”,而是“交出控制权给下一个处理器”
c.Next() 是中间件机制的中枢。它不保证一定会执行后续中间件——如果上游已调 c.Abort(),c.Next() 就直接返回,什么也不做。
- 日志中间件里
c.Next()后面的代码,只有等 handler 执行完才运行;但如果 handler panic 且没被Recovery拦住,这部分后置逻辑可能根本不会执行 - 性能敏感场景慎用耗时操作放在
c.Next()前:比如远程鉴权服务超时,会阻塞整个请求链 -
c.Next()之后还能读写c.Writer.Status()和c.Writer.Size(),这是记录真实响应状态的唯一时机 - 别在
c.Next()后修改请求体(如c.Request.Body),此时 body 已被 handler 读过,再次读会返回空
真正难的不是写一个中间件,而是判断该在哪一层注册、是否要提前 abort、以及如何安全地在 c.Next() 前后共享状态——这些细节不靠调试,靠对责任链模型的肌肉记忆。


















