3. 系统整体架构设计:RBAC权限系统分层与模块划分

做RBAC系统,最怕一上来就写代码。我在项目里踩过这个坑——没有整体架构直接开干,结果权限判断散落在业务代码各个角落,后期要改个角色继承逻辑,简直要翻遍整个项目。后来我学乖了,先画架构图,再分模块,最后才落代码。

说白了,RBAC权限系统不是独立存在的。它必须嵌入到你的业务系统里,但又不能和业务逻辑纠缠太深。我个人习惯分四层来思考:接入层、策略层、数据层、审计层。这四层各管各的事,互相通过接口通信,耦合度降到最低。

3.1 四层架构总览

先看整体结构,我画了一张图帮你理解。这张图是我做过的几个项目后总结出来的通用模式,基本能覆盖大部分业务场景。

RBAC权限系统四层架构 RBAC 权限系统四层架构 接入层 (Gateway / Filter / Interceptor) API网关 / Servlet Filter / Spring Interceptor / 中间件钩子 策略层 (Policy Engine) Subject · Resource · Action · Environment → 策略匹配引擎 支持RBAC核心模型 + 自定义策略扩展点 数据层 (Data Service) 用户 · 角色 · 权限 · 资源 · 策略 的CRUD + 缓存 关系数据库 (MySQL/PostgreSQL) + Redis缓存加速 审计层 (Audit & Monitor) 操作日志 · 授权变更审计 · 性能监控 · 异常告警 无业务侵入 纯策略计算 数据独立 事后可追溯

这张图的核心思想:请求从上往下穿透,每一层只关注自己的职责。接入层不碰业务逻辑,策略层不存数据,数据层不做决策,审计层只记录。你想想看,这样是不是特别好维护?

3.2 接入层:请求拦截与上下文构建

接入层是RBAC系统的第一道门。它负责把每个请求里的用户身份、请求路径、HTTP方法等信息提取出来,包装成一个统一的「权限上下文」对象。

我之前见过一个项目直接在业务Controller里写if(user.hasRole("admin")),这种写法有多坑我就不多说了。正确的做法是在Filter或Interceptor里统一处理。

核心职责:
· 从请求中获取用户身份(JWT / Session / )
· 从数据库中查询用户关联的角色和权限缓存
· 构建 Subject 对象,存放 userId、roleIds、permissionTags
· 构建 ResourceAction 对象,存放请求的 URI + Method + 参数
· 将 Subject 和 ResourceAction 传递给策略层
我个人建议在网关层面做这件事,比如Spring Cloud Gateway或者自研的过滤器。这样后端微服务根本不需要知道权限系统怎么运行的,无侵入性最强。

3.3 策略层:核心决策引擎

策略层是整个系统的脑髓。它接收接入层传过来的 Subject 和 ResourceAction,然后执行策略匹配。

RBAC 的基本决策逻辑其实很简单:


def check_access(subject, resource_action):
    # 1. 获取用户所有角色的权限集合
    role_ids = subject.role_ids
    permissions = permission_cache.get_permissions_by_roles(role_ids)
    
    # 2. 判断请求的资源+操作是否在权限集合中
    required = (resource_action.resource_code, resource_action.action_code)
    if required in permissions:
        return True
    # 3. 特殊策略:比如上级审批、白名单、IP限制等
    return extra_policy_check(subject, resource_action)

嗯,这里要注意一个陷阱——角色继承和权限合并。很多RBAC系统允许角色之间继承关系,比如「管理员」继承「普通用户」的所有权限。在一开始设计策略引擎时,就要把继承的展开逻辑放在数据加载阶段,而不是每次检查都去递归,否则性能会崩。

我曾经在项目中遇到过:一个用户有3个角色,每个角色又继承自其他角色,最深嵌套了5层。每次鉴权都要递归遍历角色树,响应时间从2ms暴涨到80ms。后来我们在用户登录时,就把所有展开后的权限集合一次性缓存到Redis里,问题才解决。

3.4 数据层:存储与缓存设计

数据层主要管五张核心表:用户表、角色表、权限表、用户-角色关联表、角色-权限关联表。有继承关系的还需要一张角色继承关系表。

这里我列一个推荐的表结构设计,你在项目里可以直接参考:

表名 核心字段 说明
user id, username, password_hash, status 不存角色信息,通过关联表获取
role id, role_code, role_name, level, parent_id parent_id实现角色继承
permission id, perm_code, resource_code, action_code, description resource_code + action_code 唯一
user_role user_id, role_id 多对多关联
role_perm role_id, perm_id 多对多关联
role_hierarchy ancestor_id, descendant_id, depth 闭包表,方便快速查询所有祖先/后代

缓存策略上,我习惯用「双缓存 + 失效通知」的模式:用户登录时把该用户所有展开后的权限集合写入Redis,同时用数据库的updated_at时间戳做增量同步。一旦后台修改了角色-权限关系,就主动删除对应用户的缓存,下次请求时重新加载。

3.5 审计层:一切都要留痕

这一层很容易被忽略,但真出了权限事故,你就知道它多重要了。审计层要记录:谁在什么时间访问了什么资源、结果是被允许还是被拒绝了、是哪个策略生效的。

我个人习惯把审计日志分两部分存储:

  • 操作日志:记录每一次鉴权请求的结果,用于排查异常和性能分析。用Elasticsearch或MongoDB存,查询快。
  • 变更日志:记录管理员对角色、权限、用户-角色关系的所有修改。用普通关系表存,因为要支持事务完整性。
避坑指南:
我曾经因为没记录变更日志,被客户问到「这个管理员是谁在什么时候加上的?」——完全答不上来。从那以后,我把所有数据层的写操作都切成了事件驱动的记录模式,每条数据变更都有审计轨迹。

3.6 模块划分与接口映射

有了分层架构,接下来就是模块划分。我通常把整个权限系统拆成以下几个模块:

  1. 权限管理模块:对外提供权限配置的后台API,比如创建角色、分配权限、查看策略。
  2. 鉴权服务模块:对内提供鉴权接口,业务系统调用它来判断当前用户能否执行某个操作。
  3. 数据同步模块:处理缓存和数据库之间的同步,以及跨服务之间的权限数据分发。
  4. 审计模块:负责日志采集、存储和查询。

接口映射上,我建议遵循一个原则:鉴权接口只问「能不能」,不问「是谁」。也就是说,业务系统只需要传一个(userId, resourceCode, actionCode)三元组,鉴权服务返回true/false。角色是谁、策略怎么配,那是权限管理模块的事,业务系统根本不需要知道。

举个例子:你有一个订单系统的「取消订单」按钮,业务代码里直接调 authService.check(userId, "order", "cancel")。如果返回false,就灰显按钮。这样哪怕权限模型从RBAC换成了ABAC,业务代码一行都不用改。

接口映射时还需注意批量鉴权的场景。一个页面可能有十几个按钮都需要鉴权,一个一个调太慢了。我一般会提供一个批量接口,传入多个(resource, action)对,一次返回所有结果。说白了,就是空间换时间。

好了,这四层架构和模块划分基本覆盖了RBAC权限系统从接收请求到返回结果的完整链路。下一层咱们具体讲数据模型的设计,我在那章会给你看完整的建表SQL和索引优化策略。