中间件注册位置决定作用范围:全局用r.Use()、路由组用group.Use()或Group()参数、单路由直接传参;必须在r.Run()前注册,且需调用c.Next()或c.Abort()控制执行流。

中间件注册位置决定作用范围
中间件不是写完就能生效的,必须在 r.Run() 之前、且明确绑定到某个作用域(全局 / 路由组 / 单个路由)才起作用。Gin 的路由树是静态编译的,运行时调用 r.Group() 或 r.Use() 会 panic。
- 全局中间件:用
r.Use()注册,对所有后续注册的路由生效 - 路由组中间件:用
group.Use()或r.Group("/path", middleware),只对该 group 及其子 group 生效 - 单个路由中间件:直接在
r.GET()等方法中作为参数传入,仅对该路由生效 - 错误示例:
if c.Request.URL.Path == "/admin" { r.Use(auth) }—— 这在 handler 里执行,完全无效
中间件函数必须返回 gin.HandlerFunc
自定义中间件本质是一个接收 *gin.Context 并无返回值的函数,但 Gin 要求它必须是 gin.HandlerFunc 类型。有两种常见写法:
- 直接定义函数:
func AuthMiddleware(c *gin.Context) { ... },注册时传AuthMiddleware(不带括号) - 闭包工厂函数:
func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { ... } },注册时传AuthMiddleware()(带括号) - 关键区别:后者支持传参(如
AuthMiddleware("admin")),前者不能 - 漏掉
c.Next()会导致后续中间件和 handler 完全不执行,请求卡住
c.Next() 和 c.Abort() 控制执行流
中间件内部靠这两个方法控制洋葱模型的流转。它们不是可选的装饰,而是执行逻辑的关键开关。
-
c.Next()表示“继续执行链中下一个中间件或最终 handler”,它之后的代码属于“后置处理” -
c.Abort()表示“终止当前请求链”,后续所有中间件和 handler 都跳过;常用于鉴权失败、参数校验不通过等场景 - 常见错误:在
c.Abort()后还调用c.JSON()或c.String(),但没加return,导致 panic(因为 context 已被标记为 aborted) - 正确写法:
if !valid { c.AbortWithStatusJSON(401, gin.H{"error": "unauthorized"}); return }
安全头中间件要区分 HTTPS 环境
像 Strict-Transport-Security 这类头只应在 HTTPS 下设置,否则浏览器会忽略甚至报错。硬编码或忽略协议判断会导致线上环境安全隐患。
- 检查方式优先用
c.Request.TLS != nil,比依赖X-Forwarded-Proto更可靠(后者可能被伪造) - 其他头如
X-Frame-Options、X-Content-Type-Options可无条件设置 - 别把所有安全头塞进一个中间件然后全局启用——有些接口(如开放给第三方 iframe 嵌入的页面)需要动态绕过
- 示例中
c.Header("Content-Security-Policy", "frame-ancestors 'none'")会彻底禁止嵌入,若需白名单,得改成"frame-ancestors https://trusted.example.com"
Use() 而没继承鉴权中间件,这种问题在线上很难复现,只能靠启动阶段的结构化注册来规避。


















