--- tags: [pattern/auth, rbac, role-inheritance, separation-of-duty, access-control] create time: 2026-08-08 15:30 update time: 2026-08-08 15:30 --- # RBAC 权限模型 ## 概述 RBAC(Role-Based Access Control)是一种基于角色分配访问权限的模型,是目前企业级应用中最主流的权限管理方案。它的核心思想是**将用户与角色关联,而非将用户与权限直接绑定**——这层间接大大降低了权限管理的复杂度。 ## 五张表设计 ### 表结构定义 ```sql -- 1. 用户表 CREATE TABLE users ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(64) NOT NULL UNIQUE, password_hash VARCHAR(128) NOT NULL, email VARCHAR(128), status TINYINT DEFAULT 1 COMMENT '1-active 0-disabled', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 2. 角色表 CREATE TABLE roles ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL UNIQUE, description VARCHAR(256), is_super TINYINT DEFAULT 0 COMMENT '是否超级管理员', parent_id BIGINT DEFAULT NULL COMMENT '父角色ID(用于继承)', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 3. 权限表 CREATE TABLE permissions ( id BIGINT PRIMARY KEY AUTO_INCREMENT, code VARCHAR(128) NOT NULL UNIQUE COMMENT '权限标识如 user:create', resource VARCHAR(64) NOT NULL COMMENT '资源类型: user/order/bucket', action VARCHAR(64) NOT NULL COMMENT '操作: create/read/update/delete', description VARCHAR(256) ); -- 4. 用户-角色(多对多) CREATE TABLE user_roles ( user_id BIGINT NOT NULL, role_id BIGINT NOT NULL, PRIMARY KEY (user_id, role_id), FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (role_id) REFERENCES roles(id) ); -- 5. 角色-权限(多对多) CREATE TABLE role_permissions ( role_id BIGINT NOT NULL, permission_id BIGINT NOT NULL, PRIMARY KEY (role_id, permission_id), FOREIGN KEY (role_id) REFERENCES roles(id), FOREIGN KEY (permission_id) REFERENCES permissions(id) ); ``` ### ER 关系图 ```mermaid erDiagram users ||--o{ user_roles : "belongs to" roles ||--o{ user_roles : "assigned via" roles ||--o{ role_permissions : "has" permissions ||--o{ role_permissions : "granted through" users { BIGINT id PK varchar username varchar password_hash tinyint status } roles { BIGINT id PK varchar name varchar description tinyint is_super BIGINT parent_id FK } permissions { BIGINT id PK varchar code UK varchar resource varchar action } user_roles { BIGINT user_id PK BIGINT role_id PK } role_permissions { BIGINT role_id PK BIGINT permission_id PK } ``` ## 核心原理 ### 用户 → 角色 → 权限 的两层映射 传统 ACL(Access Control List)直接将用户和权限绑定: ``` Alice → read_order, write_order, delete_user Bob → read_order Carol → read_order, write_bucket ``` 问题在于当需要批量授权时(比如给所有财务加 20 个权限),必须逐一修改每个用户的权限列表。 RBAC 引入角色层后变成: ``` 角色定义: Finance → 20 个权限(包括 read_order, export_report...) Operator → 10 个权限(read_order, write_bucket...) 用户分配: Alice → Finance Bob → Operator Carol → Operator ``` 批量调整只需改角色的权限映射,不影响用户。 ### 超级管理员与普通角色的继承 超级管理员通常拥有所有权限。但直接给 super_admin 角色分配几百条权限记录既繁琐也不优雅。更合理的做法是**继承机制**: ```sql -- 在 roles 表中用 parent_id 表示层级 INSERT INTO roles (name, is_super, parent_id) VALUES ('super_admin', 1, NULL); INSERT INTO roles (name, is_super, parent_id) VALUES ('admin', 0, (SELECT id FROM roles WHERE name='super_admin')); INSERT INTO roles (name, is_super, parent_id) VALUES ('finance', 0, (SELECT id FROM roles WHERE name='admin')); ``` 查询某用户全部权限时递归展开: ```go func (m *AuthManager) GetUserPermissions(ctx context.Context, userID int64) ([]string, error) { // 1. 查用户所有角色 roles, _ := m.db.QueryContext(ctx, ` SELECT r.id, r.is_super, r.parent_id FROM user_roles ur JOIN roles r ON ur.role_id = r.id WHERE ur.user_id = ?`, userID) var allCodes []string for roles.Next() { var role Role roles.Scan(&role.ID, &role.IsSuper, &role.ParentID) if role.IsSuper { return []string{"*"}, nil } perms, _ := m.getRolePermissionCodes(ctx, role.ID) allCodes = append(allCodes, perms...) } return deduplicate(allCodes), nil } ``` ### 互斥职责分离(Separation of Duty) 某些场景下同一人不能同时拥有冲突的角色,例如: - **申请人与审批人不可同一人** — 你不能批准自己的报销单 - **出纳与会计不可兼任** — 财务内控基本要求 实现方式是在角色创建或用户分配时做冲突检测: ```sql -- 假设 conflict_roles 表定义互斥关系 CREATE TABLE conflict_roles ( role_a BIGINT NOT NULL, role_b BIGINT NOT NULL, PRIMARY KEY (role_a, role_b) ); -- 分配角色前检查 SELECT COUNT(*) FROM conflict_roles cr JOIN user_roles ur ON ur.role_id IN (cr.role_a, cr.role_b) WHERE ur.user_id = ? AND cr.role_a = ? AND cr.role_b = ? ``` > [!WARNING] > 互斥约束应该在**分配角色层**校验,而不是每次鉴权时检查。前者是策略问题,后者是执行问题——在入口处挡住比到处拦击效率高得多。 ### 动态权限加载流程 ```mermaid sequenceDiagram participant User as 用户 participant API as 认证服务 participant DB as 数据库 participant Redis as 缓存 User->>API: 登录 API->>DB: 查询用户所有角色 DB-->>API: 返回角色列表 API->>DB: 查询各角色的权限码 DB-->>API: 返回权限码列表 API->>Redis: 写入用户权限缓存 (TTL=2h) Redis-->>API: OK API-->>User: 登录成功 + Token ``` > [!TIP] > 登录时将完整权限树缓存在 Redis 中,鉴权接口只需 O(1) 读取缓存。权限变更时主动删除对应用户的缓存条目,实现最终一致。不要每次都查库。 ## 代码示例:权限中间件(Go) ```go type ContextKey string const PermissionKey ContextKey = "permissions" func AuthMiddleware(h Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { token := extractToken(r) claims, _ := jwt.Parse(token) userID := claims.UserID // 从 Redis 获取权限 perms, err := cache.GetPermissions(r.Context(), userID) if err != nil { perms = loadFromDB(userID) cache.Set(userID, perms, 2*time.Hour) } ctx := context.WithValue(r.Context(), PermissionKey, perms) h.ServeHTTP(w, r.WithContext(ctx)) } } // 检查某个权限 func HasPermission(ctx context.Context, required string) bool { perms := ctx.Value(PermissionKey).([]string) for _, p := range perms { if p == "*" || p == required { return true } } return false } ``` ## 实践场景 | 场景 | 选型建议 | |------|---------| | 小型系统(用户 < 100) | 简单角色 + 硬编码判断即可,不必上完整 RBAC | | 中型业务系统 | 标准五表 RBAC + Redis 缓存 | | 平台型 SaaS | RBAC + 租户隔离(每租户可自定义角色) | | 强合规要求(金融/医疗) | RBAC + SoD 互斥约束 + 完整审计日志 | ## 关联笔记 - [[OAuth2 与 JWT]]