Go项目初期选RBAC即可,结构清晰易实现;ABAC仅在需动态条件(如部门匹配)时引入。权限校验须前置中间件,缓存权限至context或Redis,权限字符串统一用resource:action格式。

权限模型怎么选:RBAC 还是 ABAC?
大多数 Go 项目初期用 RBAC(基于角色的访问控制)就够了,它结构清晰、易实现、数据库建模简单。ABAC 虽灵活,但规则分散、调试困难,除非你明确需要「用户部门 == 当前资源所属部门」这类动态条件,否则别过早引入。
Go 里不用硬套框架,直接用结构体 + 映射就能跑通 RBAC 核心链路:
-
Role表存角色名(如"admin"、"editor") -
Permission表存权限标识(如"user:read"、"post:delete") -
role_permissions关联表做多对多绑定 - 用户查权限时,先查
user_role得到角色 ID,再 JOIN 查对应所有permission_code
中间件里怎么校验权限:别在 handler 里写 if err != nil
权限检查必须前置,且失败要立即中断请求。推荐用 Gin 或 Echo 的中间件模式,在路由进入业务逻辑前完成校验。
关键点:
立即学习“go语言免费学习笔记(深入)”;
- 从
context提取当前用户(通常来自 JWT 解析或 session),不是从 query/body 里临时读 - 权限检查函数返回
bool, error,error用于区分「无权限」和「系统错误」——前者返回403,后者返回500 - 避免每次请求都查库:把用户角色+权限缓存在
context.Value或提前加载进user struct字段里
func AuthMiddleware(requiredPerm string) gin.HandlerFunc {
return func(c *gin.Context) {
user, ok := c.Get("user").(*User)
if !ok {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "no user in context"})
return
}
if !user.HasPermission(requiredPerm) { // 内存中查 map[string]bool
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "insufficient permissions"})
return
}
c.Next()
}
}
权限字符串怎么设计:用冒号分隔,别用下划线或斜杠
权限标识建议统一用 resource:action 格式,例如 "order:create"、"report:export"。这种结构天然支持前缀匹配(比如 "order:*" 表示订单全部操作),也方便前端按模块过滤按钮。
避坑提示:
- 不要用
order_create—— 无法语义化分组,正则难写 - 不要用
/api/v1/orders—— 把路径耦合进权限,REST 变更就崩 - 不要省略 action,只写
"order"—— 后期没法细化读/写/删 - 大小写敏感:统一小写,避免
"User:Read"和"user:read"被当成两个权限
数据库查询权限太慢?预加载比 JOIN 更稳
用户登录后,一次性查出所有权限并缓存在内存,比每次接口都 JOIN role_permissions 快得多,也避免 N+1 查询。
实操建议:
- 用户登录成功后,调用
LoadUserPermissions(userID),返回map[string]bool(key 是"post:delete") - 这个 map 存进 Redis 用
userID:permissions作 key,过期时间设为 24h,比 session 长一点,避免频繁重载 - 如果权限变更频繁(比如后台实时改角色),加个
invalidatePermissionCache(userID)主动删 Redis key - 别用 GORM 的
Preload在每次请求时懒加载权限——它会触发额外 SQL,且难以控制缓存粒度
最常被忽略的是权限变更后的缓存一致性。上线后如果发现改了角色但接口还是 403,第一反应不是代码逻辑错,而是 Redis 里那条 permission 缓存还没过期。










