4、授权模型(下):基于角色的访问控制(RBAC)模型与实战设计

上一章我们聊了 ACL 和 DAC,说白了就是「谁对什么能做什么」的直白映射。但实际企业里,用户成百上千,资源成千上万,一个个配权限?那不得配到天荒地老。

所以就有了 RBAC——基于角色的访问控制。这是我个人最常用的模型,也是目前企业级系统的标配。今天咱们就把它拆开揉碎了讲清楚。

4.1 为什么需要角色?

先想一个问题:一个公司有 500 个员工,每个员工都需要访问不同的系统资源。如果按 ACL 的方式,你得维护 500 条甚至更多的权限记录。员工离职、转岗、入职,每次都要改一堆配置。

这太累了。你想想看,现实世界里我们怎么管权限的?

  • 新员工入职,HR 说「给他开个开发账号」
  • 员工转岗,主管说「把他从开发组调到运维组」
  • 离职了,直接「把这个账号禁用」

这里面的「开发账号」「运维组」就是角色。角色把权限打包成一个「职位」,用户只要被赋予这个角色,就自动获得对应的权限。这就是 RBAC 的核心思想。

核心公式:用户 → 角色 → 权限

用户不直接拥有权限,而是通过角色间接获得。角色是权限的集合,用户是角色的集合。

4.2 RBAC 的三大核心要素

RBAC 模型里,有三个东西你必须搞清楚:用户、角色、权限。它们之间的关系,我用一张图给你说明白。

RBAC 核心三要素关系图 用户 (User) 角色 (Role) 权限 (Permission) 被赋予 包含 说明: • 一个用户可以拥有多个角色(如:既是开发又是项目管理员) • 一个角色可以包含多个权限(如:开发角色包含代码读取、写入权限) • 一个权限可以被多个角色共享(如:读取日志权限,开发和运维都需要) • 用户通过角色间接获得权限,不直接绑定权限 • 权限变更时,只需修改角色定义,所有关联用户自动生效

嗯,这张图基本把 RBAC 的骨架画清楚了。但实际落地的时候,还有几个细节要注意。

4.3 RBAC 的三种常见变体

RBAC 不是只有一种玩法。根据 NIST 标准,它分三个级别。我直接给你列个表,对比着看更清楚。

级别 名称 核心特点 适用场景
RBAC0 基础 RBAC 用户 ↔ 角色 ↔ 权限,无继承 小型系统、简单权限管理
RBAC1 角色分级 RBAC 角色支持继承(上级角色自动拥有下级权限) 层级组织(如:经理 → 主管 → 员工)
RBAC2 约束 RBAC 增加职责分离、互斥角色等约束 金融、合规要求高的系统

我的经验:大多数项目从 RBAC0 起步就够了。别一上来就搞角色继承和互斥约束,容易把自己绕进去。我见过一个团队,RBAC1 的角色继承搞了五层,最后没人说得清某个角色到底有哪些权限。后来全推倒重来。

4.4 实战设计:一个 RBAC 权限系统的数据库模型

光说不练假把式。咱们直接上手设计一个 RBAC 的数据库模型。这是我在多个项目中用过的结构,经过验证的。

-- 用户表
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE,
    password_hash VARCHAR(255) NOT NULL,
    email VARCHAR(100),
    status TINYINT DEFAULT 1,  -- 1:启用 0:禁用
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- 角色表
CREATE TABLE roles (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_name VARCHAR(50) NOT NULL UNIQUE,
    role_desc VARCHAR(200),
    parent_role_id BIGINT DEFAULT NULL,  -- RBAC1 角色继承用
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- 权限表
CREATE TABLE permissions (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    perm_code VARCHAR(100) NOT NULL UNIQUE,  -- 如:user:create, order:read
    perm_name VARCHAR(100),
    resource_type VARCHAR(50),  -- 资源类型:user, order, report
    action VARCHAR(50),         -- 操作:create, read, update, delete
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- 用户-角色关联表(多对多)
CREATE TABLE user_roles (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    role_id BIGINT NOT NULL,
    UNIQUE KEY uk_user_role (user_id, role_id),
    FOREIGN KEY (user_id) REFERENCES users(id),
    FOREIGN KEY (role_id) REFERENCES roles(id)
);

-- 角色-权限关联表(多对多)
CREATE TABLE role_permissions (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_id BIGINT NOT NULL,
    permission_id BIGINT NOT NULL,
    UNIQUE KEY uk_role_perm (role_id, permission_id),
    FOREIGN KEY (role_id) REFERENCES roles(id),
    FOREIGN KEY (permission_id) REFERENCES permissions(id)
);

避坑指南:我曾经在一个项目里把权限直接挂在用户表上,想着「省一张表」。结果用户量一上来,权限查询慢得要命,而且每次改权限都要改用户表。后来老老实实拆成五张表,性能反而好了。记住:数据库设计别贪图「省表」,该拆就拆。

4.5 权限校验的核心逻辑

模型建好了,那怎么判断一个用户有没有权限?我写个伪代码,你一看就明白。

function checkPermission(userId, requiredPermCode):
    // 1. 获取用户的所有角色
    roles = SELECT r.* FROM roles r
            JOIN user_roles ur ON r.id = ur.role_id
            WHERE ur.user_id = userId

    // 2. 如果是 RBAC1,递归获取所有父角色
    allRoles = getAllParentRoles(roles)

    // 3. 获取这些角色拥有的所有权限
    permissions = SELECT p.* FROM permissions p
                  JOIN role_permissions rp ON p.id = rp.permission_id
                  WHERE rp.role_id IN (allRoles)

    // 4. 检查是否包含所需权限
    return permissions.contains(requiredPermCode)

这个逻辑看着简单,但实际落地时有个坑:角色继承的递归深度。我建议最多三层,超过三层就考虑用扁平化设计,否则递归查询能把数据库拖垮。

4.6 角色互斥与职责分离

RBAC2 里有个重要概念叫「职责分离」。说白了就是:不能让同一个人同时拥有两个互斥的角色。

举个例子:

  • 「采购员」和「验收员」不能是同一个人
  • 「审批人」和「执行人」不能是同一个人

实现方式很简单,加一张互斥角色表:

CREATE TABLE role_exclusions (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_id_a BIGINT NOT NULL,
    role_id_b BIGINT NOT NULL,
    UNIQUE KEY uk_exclusion (role_id_a, role_id_b),
    FOREIGN KEY (role_id_a) REFERENCES roles(id),
    FOREIGN KEY (role_id_b) REFERENCES roles(id)
);

给用户分配角色时,先查一下这个用户已有的角色,和新角色有没有互斥关系。有的话直接拒绝。

我踩过的坑:有一次做金融系统,忘了加互斥校验。结果一个运维同事同时拥有「数据导出」和「日志审计」两个角色,他导出了客户数据然后自己删了审计日志。虽然最后查出来了,但那次事故让我记住了:涉及敏感操作的权限,一定要做职责分离。

4.7 RBAC 的优缺点总结

说了这么多,最后给你总结一下 RBAC 的优缺点,方便你选型时参考。

维度 优点 缺点
管理效率 批量管理,角色一变所有用户跟着变 角色数量过多时,管理成本上升
灵活性 支持多角色、角色继承、互斥约束 细粒度权限控制不够灵活(如:只允许某用户查看某条数据)
安全性 职责分离天然支持合规要求 角色爆炸后,权限审计困难
实现复杂度 模型清晰,数据库设计简单 递归继承和互斥校验增加代码复杂度

RBAC 不是万能的。如果你的系统需要「用户 A 只能看自己的订单,用户 B 能看所有人的订单」这种细粒度控制,RBAC 就有点吃力了。这时候需要引入 ABAC(基于属性的访问控制),那是后面章节的内容。

但话说回来,80% 的企业系统用 RBAC 就够了。别为了 20% 的场景把架构搞得太复杂。先跑起来,再优化,这是我的原则。