设计一个权限系统(RBAC + ABAC)

本文系统设计一个大规模权限系统:RBAC 角色权限模型与 ABAC 属性策略的融合、权限数据模型与多租户隔离、鉴权性能优化(缓存/布隆/负缓存)、细粒度数据权限(行级)、权限变更的实时生效与审计,并给出架构图、数据表、鉴权伪代码与量级估算。

权限系统是每一个后台系统的「安全地基」:谁(用户)能对什么(资源)做什么(操作),看起来简单,但一旦用户上千万、角色上万、资源量上亿,就要在「功能权限(菜单/按钮)」与「数据权限(行级)」、在「灵活的策略表达」与「毫秒级鉴权性能」之间做系统设计。本文按照系统设计面试的标准答题结构,设计一个融合 RBAC 与 ABAC 的大规模权限系统。

一句话:权限系统的核心是「模型 + 性能」——RBAC 用角色表达稳定的功能权限,ABAC 用属性策略表达灵活的数据权限,缓存与布隆过滤器让亿级数据的鉴权仍然毫秒级完成。

一、需求澄清与量级估算

1.1 需求澄清

面试官给出题目「设计一个权限系统」后,先通过提问明确边界:

  • 权限模型:RBAC(角色-权限)、ABAC(属性-策略)、还是二者融合?
  • 权限粒度:功能权限(菜单/按钮/接口)、数据权限(行级/字段级)都要吗?
  • 用户规模:多少用户、多少角色、多少租户?
  • 动态性:权限变更(授权/回收/角色调整)需要多快生效?
  • 多租户:不同租户是否隔离权限数据?超管是否跨租户?
  • 合规:敏感操作是否需要审计留痕?

明确假设(面向面试的合理假设):

需求项假设
权限模型RBAC 为主 + ABAC 扩展数据权限
权限粒度功能权限(菜单/按钮/接口)+ 行级数据权限
用户规模1000 万用户、1 万角色、10 万租户
动态性授权变更 30 秒内生效(准实时)
多租户权限数据按租户隔离,平台超管可跨租户
审计敏感操作全量留痕

1.2 量级估算

指标估算值推导
用户数1000 万—
角色数1 万各租户自定义角色
权限点5 万功能权限点(菜单/按钮/API)
用户-角色关系3000 万平均每用户 3 个角色
鉴权 QPS30 万后台/业务接口高频鉴权
鉴权延迟< 10ms业务可接受
数据权限行亿级行级权限涉及资源表

一句话:千万用户 × 万级角色 × 亿级数据行的组合,决定了鉴权绝不能实时查表,必须「预计算权限快照 + 多级缓存」走热路径。

二、高层架构设计

   ┌────────────────────────────┐   ┌────────────────────────────┐
   │ 业务系统(调用鉴权)         │   │ 管理后台(授权操作)         │
   │ 功能鉴权 / 数据鉴权 / 审计  │   │ 用户/角色/权限/策略管理      │
   └─────────────┬──────────────┘   └─────────────┬──────────────┘
                 │ 鉴权请求                         │ 变更指令
   ┌─────────────▼─────────────────────────────────▼──────────────┐
   │                        鉴权服务(AuthZ)                        │
   │  ┌─────────────┐  ┌─────────────┐  ┌───────────────────────┐ │
   │  │ 功能鉴权     │  │ 数据鉴权     │  │ 策略引擎(ABAC)       │ │
   │  │ (权限快照)   │  │ (行级过滤)   │  │ (属性匹配/规则求值)    │ │
   │  └─────────────┘  └─────────────┘  └───────────────────────┘ │
   │  ┌─────────────┐  ┌─────────────┐  ┌───────────────────────┐ │
   │  │ 用户权限快照 │  │ 数据权限解析 │  │ 审计服务              │ │
   │  │ (缓存生成)  │  │ (SQL改写)   │  │ (敏感操作留痕)         │ │
   │  └─────────────┘  └─────────────┘  └───────────────────────┘ │
   └───────┬───────────────────────┬──────────────────────┬───────┘
           │ 预计算                 │ 策略数据               │ 审计
   ┌───────▼───────┐   ┌───────────▼────────┐   ┌──────────▼───────┐
   │ Redis 权限缓存 │   │ 权限元数据 MySQL    │   │ 审计日志存储      │
   │ 快照/布隆/负缓存│   │ 用户/角色/权限/策略 │   │ (Kafka+数仓/ES)  │
   └───────────────┘   └───────────────────┘   └──────────────────┘
        ┌─────────────────────────────────────────────┐
        │ 变更通知总线(MQ):授权变更 → 缓存失效 → 重算 │
        └─────────────────────────────────────────────┘

整体拆为五个模块:

  1. 接入层:业务系统调用鉴权 SDK/网关,功能鉴权走热路径。
  2. 鉴权服务:功能鉴权(权限快照)、数据鉴权(行级 SQL 改写)、策略引擎(ABAC)。
  3. 权限元数据:MySQL 存储用户-角色-权限-策略关系。
  4. 缓存层:Redis 存权限快照、布隆过滤器与负缓存。
  5. 变更与审计:MQ 通知缓存失效重算,审计服务全量留痕。

2.1 RBAC + ABAC 融合模型

  • RBAC 管「功能权限」:稳定、好理解、可授予——「运营角色能看订单管理菜单」。
  • ABAC 管「数据权限」:灵活、属性化——「只能看自己部门的订单」「金额超过 10 万的需风控审批」。
功能权限(RBAC):用户 → 角色 → 权限点(菜单/按钮/API)→ 是否允许
数据权限(ABAC):策略 = 主体属性 + 资源属性 + 环境属性 → 布尔裁决
鉴权请求 = 功能鉴权(快照) AND 数据鉴权(策略)

一句话:RBAC 解决「能不能用这个功能」,ABAC 解决「能用这个功能但只能看哪些数据」——两者合起来才是完整权限,缺一不可。

三、核心组件设计

3.1 权限数据模型

-- RBAC 部分
CREATE TABLE user_role (
  user_id    BIGINT,
  role_id    BIGINT,
  tenant_id  BIGINT,           -- 多租户隔离键
  PRIMARY KEY (user_id, role_id, tenant_id)
);

CREATE TABLE role (
  role_id    BIGINT PRIMARY KEY,
  tenant_id  BIGINT,
  name       VARCHAR(64),
  status     TINYINT
);

CREATE TABLE role_perm (
  role_id    BIGINT,
  perm_id    BIGINT,
  PRIMARY KEY (role_id, perm_id)
);

CREATE TABLE permission (
  perm_id    BIGINT PRIMARY KEY,
  code       VARCHAR(64),      -- 权限点编码,如 "order:read"
  resource   VARCHAR(64),      -- 资源类型
  action     VARCHAR(32),      -- 操作类型
  status     TINYINT
);

-- ABAC 部分:策略即规则
CREATE TABLE auth_policy (
  policy_id    BIGINT PRIMARY KEY,
  tenant_id    BIGINT,
  resource     VARCHAR(64),    -- 目标资源
  effect       TINYINT,        -- 1允许 0拒绝
  condition_json JSON,         -- 属性条件(表达式树)
  priority     INT,            -- 优先级
  status       TINYINT
);

ABAC 策略条件用 JSON 表达式树表达,支持任意属性组合:

{
  "resource": "order",
  "effect": 1,
  "conditions": {
    "all": [
      { "subject": "user.department", "op": "==", "value": "resource.order.department" },
      { "environment": "time", "op": "between", "value": ["09:00", "18:00"] }
    ]
  }
}

3.2 功能鉴权:权限快照与多级缓存

功能鉴权是最高频调用,绝不能每次查 MySQL。核心是预计算权限快照:

权限快照:用户在某租户下的「权限点集合」(合并所有角色的权限)
缓存层次:
  ① 本地缓存(每鉴权节点):毫秒级,存最近 N 用户的快照
  ② Redis:全局共享,快照 + TTL
  ③ MySQL:兜底,快照未命中时实时组装

查询路径:
  perm-check(user, tenant, permCode)
  本地缓存 → Redis → 组装快照(MySQL join)并回填缓存
;; 伪代码:功能鉴权(多级缓存)
(defn check-permission [user tenant perm]
  (let [cached (or (local-get user tenant)       ; 本地缓存
                   (redis-get "authz:snap:" user tenant))]
    (if cached
      (contains? cached perm)                    ; 集合判定 O(1)
      (let [snap (build-snapshot user tenant)]   ; 回源组装
        (fill-caches user tenant snap)
        (contains? snap perm)))))

;; 快照组装(SQL 合并用户所有角色权限)
(defn build-snapshot [user tenant]
  (SELECT DISTINCT p.code
   FROM user_role ur
   JOIN role_perm rp ON ur.role_id = rp.role_id
   JOIN permission p ON rp.perm_id = p.perm_id
   WHERE ur.user_id = ? AND ur.tenant_id = ?))

3.3 数据权限:行级过滤与 SQL 改写

数据权限要让查询「只返回有权限的行」。两种主流实现:

方案A(SQL 改写 / 注入条件):
  业务查询前,鉴权服务解析数据权限策略 → 生成 WHERE 条件
  例:订单查询自动追加 " AND department_id = 用户所在部门"
  适用:关系型数据库、权限条件可表达为谓词

方案B(后置过滤):
  查出结果后逐行用策略过滤
  适用:非结构化数据、无法改写 SQL 的场景(但性能差,慎用)
数据权限解析流程:
  请求资源(order)+ 操作(read)
  → 加载该资源上对该用户生效的策略(按 priority 排序)
  → 求值条件表达式(属性环境注入)
  → 生成行级过滤谓词 → 注入业务查询 → 返回过滤后结果

要点:行级权限的性能关键在「策略也走缓存」——用户在某资源上的生效策略预计算缓存,SQL 改写才能做到每次查询仅一次策略求值。

3.4 权限变更的实时生效

权限变更(新授权/角色调整)要准实时生效,走「变更通知 + 缓存失效」:

变更链路:
  管理后台授权 → 写 MySQL(事务) → 发 MQ 变更事件
  → 鉴权节点消费 → 失效本地缓存 + Redis 快照 → 按需重算

生效时效:秒级~30 秒内
一致性取舍:权限变更允许短暂不一致(30 秒内),换取消引复杂度
;; 伪代码:变更事件处理
(defn on-permission-change [event]
  (redis/del (str "authz:snap:" (:user event) ":" (:tenant event)))
  (local-evict (:user event) (:tenant event)))

3.5 多租户隔离与超管

隔离粒度:
  数据模型:所有权限表带 tenant_id 键
  超管:平台级超管(跨租户),通过「超管角色 + tenant=0」表达
  越权防护:租户切换校验,防止跨租户授权/读取

审计:
  敏感操作(授权/回收/越权尝试)全量落审计日志
  审计与权限数据解耦:Kafka → 数仓/ES,支持追溯

四、深入权衡

4.1 缓存粒度:全量快照 vs 增量差集

方案内存失效成本适用
全量权限点快照中等(每用户几十个点)变一次全失效通用场景
权限点布隆过滤器极小变一次重建权限点极多
增量差集缓存更省复杂、易错权限点海量

结论:默认用「全量快照 + 负缓存」,权限点多到单快照膨胀时再降级为布隆过滤器;负缓存(缓存「无权限」结果)防穿透,是鉴权热路径的关键优化。

4.2 RBAC vs ABAC 的取舍

模型优点缺点适用
RBAC直观、好授权、好审计角色爆炸、数据权限难表达功能权限、组织简单
ABAC灵活、细粒度、规则化难排查、性能开销、表达复杂数据权限、多条件场景

结论:面试与工程实践都应融合——RBAC 做稳定的功能层,ABAC 做灵活的数据层;ABAC 策略不要滥用(策略越多越难维护),能用角色表达的绝不用策略。

4.3 一致性 vs 生效速度

权限变更要求「最终一致 + 准实时」,不是强一致:

允许窗口:授权变更 30 秒内生效(秒级过期)
强一致代价:每次鉴权都要查主库 → 30 万 QPS 直接打崩

一句话:权限是「低频变更、高频查询」的数据,天然适合缓存 + 准实时失效——用 30 秒的一致窗口换取毫秒级鉴权性能,是工程上唯一务实的选择。

五、总结

权限系统设计的关键是「模型融合 + 性能工程」:RBAC 角色模型承载稳定可审计的功能权限,ABAC 属性策略承载灵活的行级数据权限,两者通过统一的鉴权服务对外输出裁决。性能上,功能鉴权走「本地缓存 + Redis 快照 + 负缓存」多级缓存、预计算权限快照;数据鉴权走「策略预加载 + SQL 改写」,让亿级数据行的高频查询仍然毫秒级完成。变更通过 MQ 准实时失效缓存(30 秒窗口),审计全量留痕,多租户靠 tenant_id 隔离。最终,权限系统以「模型清晰、性能达标、变更可控、审计可溯」的姿态,成为业务系统最可靠的安全地基。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「design」更多文章

  1. 设计一个弹幕系统
  2. 设计一个分布式任务调度系统
  3. 设计一个内容审核系统