直接用 fiber.Router 做中间件注册会丢失权限上下文,因为 Use() 和 Get()/Post() 的注册顺序决定执行链,若权限中间件未在路由组顶层注册或置于路由方法之后,则不会执行;正确做法是通过 Group() 统一绑定前缀与权限中间件,并确保 authMiddleware 返回 fiber.Handler 且调用 c.Next()。

为什么直接用 fiber.Router 做中间件注册会丢失权限上下文?
因为 Fiber 的 Use() 和 Get()/Post() 注册顺序决定中间件执行链——如果权限中间件没在路由组顶层注册,或被写在了 app.Get() 之后,它根本不会被执行。常见错误是把鉴权逻辑塞进 handler 里手动校验,结果每个接口都重复写 if !hasPermission(...),既难维护又容易漏。
正确做法是用 fiber.Router.Group() 绑定统一前缀 + 权限中间件:
admin := app.Group("/admin", authMiddleware("admin"))
admin.Get("/users", getUsers)
admin.Post("/posts", createPost)
注意:authMiddleware 必须返回 fiber.Handler 类型函数,且内部调用 c.Next() 才能继续后续处理;不调用就直接中断请求。
authMiddleware 怎么从 JWT 或 Header 提取角色并校验?
Fiber 默认不解析 JWT,得自己用 github.com/golang-jwt/jwt/v5 解码。别依赖 c.Locals 存原始 token 字符串——应该解完立刻存角色和用户 ID 到 c.Locals,后续 handler 直接取,避免重复解析。
关键点:
- 从
c.Get("Authorization")取值时,要手动切掉"Bearer "前缀,否则解析失败 - 校验
exp必须用time.Now().UTC(),否则时区不对导致“明明没过期却报错” - 角色字段名(如
"role")必须和签发时一致,JWT payload 是 map[string]interface{},类型断言要加 error 检查
示例片段:
func authMiddleware(requiredRole string) fiber.Handler {
return func(c *fiber.Ctx) error {
authHeader := c.Get("Authorization")
if authHeader == "" {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "missing auth header"})
}
tokenStr := strings.TrimPrefix(authHeader, "Bearer ")
token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
return []byte(os.Getenv("JWT_SECRET")), nil
})
if err != nil || !token.Valid {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "invalid token"})
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "invalid claims"})
}
role, ok := claims["role"].(string)
if !ok || role != requiredRole {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "insufficient permissions"})
}
c.Locals("user_id", claims["user_id"])
return c.Next()
}
}
多个角色怎么配置(比如 “admin” 或 “editor” 都能访问)?
别硬编码 if role == "admin" || role == "editor"——这种逻辑散落在各中间件里,改起来麻烦。应该把权限规则抽成可配置结构:
定义一个映射表:
var permissionMap = map[string][]string{
"/admin/*": {"admin"},
"/api/posts/*": {"admin", "editor"},
"/api/comments": {"admin", "editor", "user"},
}
然后在中间件里根据 c.Path() 匹配通配规则(注意:Fiber 的 Path() 返回的是原始路径,不含 query string),再检查当前角色是否在允许列表中。用 path.Match() 或简单字符串前缀判断即可,不用上正则。
容易踩的坑:
-
c.Path()返回的是带前导斜杠的路径(如"/admin/users"),而路由组前缀也是带斜杠的,匹配时别多加一个/ - 通配符
*只支持结尾,不支持中间(如/a/*/c不生效),真要复杂匹配得换库或手写逻辑 - 权限校验失败时,别只返回 status code,至少带个
{"error": "forbidden"},前端才好统一拦截
为什么用了中间件但 c.Locals 在 handler 里取不到用户信息?
最常见原因是中间件没调用 c.Next(),或者调用了但后面有 panic 导致流程中断。另一个隐蔽问题是:Fiber 的 Locals 是 per-request 的 map,生命周期只到本次请求结束,不能跨请求复用,也不能在 goroutine 里安全读写(比如异步日志里直接取 c.Locals 会 panic)。
调试建议:
- 在中间件末尾加
log.Printf("locals: %+v", c.Locals)确认是否已写入 - 在 handler 开头立刻打印
c.Locals,确认是否为空——如果空,说明中间件根本没执行或执行异常退出 - 确保中间件注册在
Group()上,而不是单个Get()后面,后者只会对那个 endpoint 生效
权限控制真正难的不是写逻辑,而是厘清「谁在什么时候能访问什么」——路径匹配、角色继承、动态权限变更这些,光靠中间件一层拦不住,得配合后端权限服务做实时校验。别把所有规则都堆在中间件里。


















