Files

7.8 KiB
Raw Permalink Blame History

tags, create time, update time
tags create time update time
pattern/auth
rbac
role-inheritance
separation-of-duty
access-control
2026-08-08 15:30 2026-08-08 15:30

RBAC 权限模型

概述

RBAC(Role-Based Access Control)是一种基于角色分配访问权限的模型,是目前企业级应用中最主流的权限管理方案。它的核心思想是将用户与角色关联,而非将用户与权限直接绑定——这层间接大大降低了权限管理的复杂度。

五张表设计

表结构定义

-- 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 关系图

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 角色分配几百条权限记录既繁琐也不优雅。更合理的做法是继承机制:

-- 在 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'));

查询某用户全部权限时递归展开:

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)

某些场景下同一人不能同时拥有冲突的角色,例如:

  • 申请人与审批人不可同一人 — 你不能批准自己的报销单
  • 出纳与会计不可兼任 — 财务内控基本要求

实现方式是在角色创建或用户分配时做冲突检测:

-- 假设 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

互斥约束应该在分配角色层校验,而不是每次鉴权时检查。前者是策略问题,后者是执行问题——在入口处挡住比到处拦击效率高得多。

动态权限加载流程

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)

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 互斥约束 + 完整审计日志

关联笔记