Fiber 无内置 RBAC 中间件,需自定义实现:拦截请求→提取用户ID→调用 authz.Can()(缓存+数据库双查)→比对权限码→403或next;角色与权限须解耦,支持多角色合并、通配符、deny优先策略。

Fiber 本身不内置 RBAC 中间件,但可以轻松用自定义中间件 + 数据库查询 + 缓存组合实现。关键不是“有没有现成组件”,而是怎么把「用户→角色→权限」链路串进请求生命周期里,并避免重复查库、误判边界。
怎么写一个带角色校验的 Fiber 中间件
核心是拦截请求、提取登录态、查角色和权限、比对所需权限码。中间件函数签名必须是 fiber.Handler,且需在路由注册前 use。
- 从
ctx.Locals("user_id")或 JWT token 中解析当前用户 ID(推荐统一放在ctx.Locals) - 调用封装好的权限检查服务(如
authz.Can(userID, "user.delete")),内部走缓存+数据库双查 - 权限不满足时直接
ctx.Status(403).JSON(fiber.Map{"error": "forbidden"})并 return - 允许通过时调用
next(ctx),否则中断流程
示例中间件片段:
func RoleBasedAuth(permission string) fiber.Handler {
return func(c *fiber.Ctx) error {
userID := c.Locals("user_id")
if userID == nil {
return c.Status(401).JSON(fiber.Map{"error": "unauthorized"})
}
if !authz.Can(userID.(uint), permission) {
return c.Status(403).JSON(fiber.Map{"error": "forbidden"})
}
return c.Next()
}
}
为什么不能只查用户表里的 role 字段
硬编码角色字段(如 user.role = "admin")会立刻卡死在真实业务里——产品经理第二天就要加「内容审核员(可审不可删)」、「区域管理员(只能管本省用户)」,你得改代码、加 if、修测试、发版。
- RBAC 要求角色与权限解耦,靠
role_permission关联表动态绑定 - 一个用户可能有多个角色,权限需合并去重,不能只取第一个
- 权限码建议用分层命名,如
user:read、order:export:csv,便于通配匹配 - 如果用
authz.Can()内部支持order:export:*这类通配,就不用每增一种导出格式都加新权限项
缓存和数据库怎么配合才不漏判
每次请求都查 3 张表(users→user_role→role_permission)太重,但全缓存又怕权限变更后不生效。
- 缓存 key 建议用
"perm:" + strconv.FormatUint(userID, 10),TTL 设为 5–10 分钟 - 当管理员修改某角色权限时,主动
redis.Del(ctx, "perm:"+userIDStr)清掉所有相关用户的缓存(或用布隆过滤器标记批量失效) - 首次缓存未命中时,查库并写入缓存;注意空结果也要缓存(防止穿透),比如设 60 秒短 TTL
- 数据库查询务必用 JOIN 或 IN 子查询一次性拉回所有权限码,别用 N+1 查询角色再循环查权限
前端传来的权限码怎么和后端校验对齐
前后端权限码命名不一致是最常见的 403 原因——比如前端按钮写 canDeleteUser,后端中间件却在查 user.remove。
- 权限码必须由后端定义并文档化,前端只消费,不生成逻辑
- 建议统一用小写 + 冒号分隔,如
sys:menu:visible、product:sku:edit - 接口路由上显式声明所需权限,比如
app.Delete("/api/v1/users/:id", handler.DeleteUser).Use(RoleBasedAuth("user:delete")) - 不要依赖前端传来的
X-PermissionHeader 做校验——它可被篡改,只能用于调试日志
真正容易被忽略的是「权限继承」和「拒绝优先」:如果有角色 A 拥有 user:read,角色 B 显式拒绝 user:read,而用户同时属于 A 和 B,那最终结果应是拒绝。这需要你在 authz.Can() 实现里支持 deny 策略,而不是简单做 OR 合并。


















