3. 权限模型与 RBAC:权限三要素与 Go 结构体设计
权限控制是网关审计的基石。在分布式系统中,RBAC(基于角色的访问控制) 是最成熟、应用最广泛的权限模型。本章从权限三要素出发,逐步推导出 RBAC 的核心设计,并给出 Go 语言中的结构体定义与代码示例。
3.1 权限三要素:用户、角色、权限
任何权限系统都围绕三个核心概念展开:
| 要素 | 定义 | 示例 |
|---|---|---|
| 用户 (User) | 访问系统的实体,可以是人、服务账号或设备 | user:zhangsan、service:payment |
| 角色 (Role) | 权限的集合,是用户与权限之间的逻辑桥梁 | admin、editor、viewer |
| 权限 (Permission) | 对某个资源执行特定操作的许可 | order:create、report:export |
核心原则:用户不直接关联权限,而是通过角色间接获得权限。这种解耦使得权限管理在规模增长时依然可控。
3.2 RBAC 模型设计
RBAC 模型定义了用户-角色-权限之间的多对多关系。标准 RBAC(NIST RBAC 核心模型)包含以下关系:
- 用户分配 (UA):用户 ↔ 角色(多对多)
- 权限分配 (PA):角色 ↔ 权限(多对多)
- 角色继承(可选):角色可以继承其他角色的权限,形成层级结构
下图展示了 RBAC 核心模型的结构与数据流:
3.3 Go 中权限结构体定义
在 Go 项目中,我们通常使用结构体来映射 RBAC 的实体关系。以下是一组经过生产验证的结构体定义:
// User 用户实体
type User struct {
ID uint64 `json:"id" gorm:"primaryKey"`
Username string `json:"username" gorm:"uniqueIndex;size:64"`
Email string `json:"email" gorm:"size:128"`
Status int8 `json:"status"` // 1:启用 0:禁用
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// Role 角色实体
type Role struct {
ID uint64 `json:"id" gorm:"primaryKey"`
Name string `json:"name" gorm:"uniqueIndex;size:64"`
Description string `json:"description" gorm:"size:256"`
ParentID *uint64 `json:"parent_id"` // 父角色ID,支持角色继承
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// Permission 权限实体
type Permission struct {
ID uint64 `json:"id" gorm:"primaryKey"`
Resource string `json:"resource" gorm:"size:128"` // 资源标识,如 "order"
Action string `json:"action" gorm:"size:64"` // 操作标识,如 "create"
Description string `json:"description" gorm:"size:256"`
CreatedAt time.Time `json:"created_at"`
}
// UserRole 用户-角色关联表
type UserRole struct {
ID uint64 `json:"id" gorm:"primaryKey"`
UserID uint64 `json:"user_id" gorm:"index"`
RoleID uint64 `json:"role_id" gorm:"index"`
}
// RolePermission 角色-权限关联表
type RolePermission struct {
ID uint64 `json:"id" gorm:"primaryKey"`
RoleID uint64 `json:"role_id" gorm:"index"`
PermissionID uint64 `json:"permission_id" gorm:"index"`
}
设计要点:
- 权限标识:使用
Resource:Action格式(如order:create),便于统一管理和通配符匹配。 - 角色继承:
ParentID字段支持角色层级,查询时需递归或使用闭包表。 - 关联表:
UserRole和RolePermission是典型的多对多中间表,索引字段确保查询性能。 - 状态控制:用户和角色都应包含状态字段,支持软禁用而不删除数据。
3.4 权限缓存与判定函数
在实际网关中,每次请求都查询数据库是不现实的。以下是一个基于缓存的权限判定函数示例:
// PermissionCache 用户权限缓存(简化版)
type PermissionCache struct {
mu sync.RWMutex
userPerms map[uint64]map[string]struct{} // userID -> set of "resource:action"
}
// HasPermission 检查用户是否拥有指定权限
func (pc *PermissionCache) HasPermission(userID uint64, resource, action string) bool {
pc.mu.RLock()
defer pc.mu.RUnlock()
perms, ok := pc.userPerms[userID]
if !ok {
return false
}
// 精确匹配
key := resource + ":" + action
if _, exists := perms[key]; exists {
return true
}
// 通配符匹配:resource:*
wildcardKey := resource + ":*"
if _, exists := perms[wildcardKey]; exists {
return true
}
// 超级管理员匹配:*:*
if _, exists := perms["*:*"]; exists {
return true
}
return false
}
// RefreshUserPerms 刷新指定用户的权限缓存
func (pc *PermissionCache) RefreshUserPerms(userID uint64, db *gorm.DB) {
var perms []string
// SQL: 通过用户ID查询所有关联权限
db.Raw(`
SELECT DISTINCT p.resource || ':' || p.action
FROM permissions p
JOIN role_permissions rp ON p.id = rp.permission_id
JOIN user_roles ur ON rp.role_id = ur.role_id
WHERE ur.user_id = ?
`, userID).Scan(&perms)
permSet := make(map[string]struct{}, len(perms))
for _, p := range perms {
permSet[p] = struct{}{}
}
pc.mu.Lock()
pc.userPerms[userID] = permSet
pc.mu.Unlock()
}
注意:上述 SQL 查询使用了 PostgreSQL 的字符串拼接语法
||。若使用 MySQL,请改用 CONCAT(p.resource, ':', p.action)。生产环境中建议将权限刷新操作放在异步队列中执行,避免阻塞主请求链路。
3.5 本章小结
| 知识点 | 关键内容 |
|---|---|
| 权限三要素 | 用户、角色、权限,三者通过 UA 和 PA 关系解耦 |
| RBAC 模型 | 用户→角色→权限的间接授权体系,支持角色继承 |
| Go 结构体 | User、Role、Permission 实体 + UserRole、RolePermission 关联表 |
| 缓存策略 | 内存缓存 + 通配符匹配,减少数据库查询压力 |