鉴权中间件必须在认证中间件之后调用,且需通过c.locals传递用户信息、权限校验失败时禁止调用next(c)、路径匹配应基于c.route().path模板而非原始路径、显式放行options和健康检查路径。

鉴权中间件必须在认证中间件之后调用
Fiber 不像 Gin 那样靠 c.Next() 控制流程,而是靠是否调用 next(c) 决定后续 handler 是否执行。但前提是:鉴权逻辑依赖的用户信息(如 user_id、role)必须已由上游认证中间件写入 c.Context()。如果顺序颠倒——比如先注册鉴权中间件、再注册 JWT 解析中间件——那 c.Locals("user_id") 就是 nil,直接 panic。
正确写法是:
app.Use(authMiddleware) // 先解析 token,存 user_id、role 到 c.Locals
app.Use(permMiddleware) // 再查权限,只读不写
app.Get("/admin", adminHandler)
注意:authMiddleware 必须用 c.Locals(不是 c.Context().Value)存数据,因为 Fiber 的 Locals 是专为中间件间传递值设计的轻量机制;permMiddleware 也必须用 c.Locals 读取,否则拿不到。
鉴权失败时不调 next(c),直接响应并 return
这是最容易踩的坑:很多人写完 c.Status(403).JSON(...) 后还顺手调了 next(c),结果后续 handler 照常执行,可能重复写响应体、触发 DB 查询、甚至返回 200 成功态。
正确做法只有三步:
- 检查权限不通过 → 立即调用
c.Status(403).SendString("forbidden")或类似响应 - 紧跟一个
return - 绝对不调
next(c)
错误示例:c.Status(403).SendString("no perm"); next(c); return —— 这等于白写 403,next(c) 已把控制权交出去。
路径匹配别用 r.URL.Path 做字符串判断
Fiber 的 c.Path() 返回的是原始请求路径(如 /users/123/、/users/123?x=1),而权限规则通常按路由模板定义(如 GET:/users/:id)。直接用 strings.Contains(c.Path(), "/admin") 会被 /administer 绕过,或漏掉带查询参数的合法请求。
推荐方式:
- 用
c.Route().Path获取注册时的模板路径(如/admin/*、/users/:id) - 对模板做标准化处理:把
:id替换为{id},*替换为{*},得到键如GET:/admin/{*} - 预热加载时就把该键映射到权限集,运行时查 map,O(1) 完成校验
避免每次请求都查 DB 或 Redis,尤其在高频接口上。
必须显式放行 OPTIONS 和健康检查路径
CORS 预检、K8s liveness probe、Prometheus metrics 等请求不该被鉴权拦截。但 Fiber 中间件默认对所有方法生效(包括 OPTIONS),所以得手动跳过:
if c.Method() == "OPTIONS" || c.Path() == "/health" {
c.Next()
return
}
// 后续做权限校验
注意:/health 要和你实际注册的路径一致;如果用了 app.Get("/v1/health", ...),这里就得写 c.Path() == "/v1/health"。别依赖 c.FullPath(),它在未匹配路由时为空字符串,不可靠。
真正难的不是写个 if !hasPerm { ... return },而是权限模型怎么跟路由模板对齐、怎么让预热加载不拖慢启动、以及如何确保每个中间件都用 Locals 而不是混用 Context.Value —— 这些细节一错,线上就静默越权或 panic。











