ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

功能权限控制实战:RBAC打底、ABAC补漏的混合方案

功能权限控制实战:RBAC打底、ABAC补漏的混合方案 功能权限控制这事儿我平时最怕听到的反馈是“这个按钮我点了没反应”或者“这个菜单我怎么看不到”。排查到最后十有八九不是前端问题而是权限模型在设计阶段就埋了雷。很多团队一上来就抄一套不知来源的权限表结果角色越加越多判断逻辑塞满了if else最后连管理员自己都分不清哪个角色能干嘛。这事我在几个项目里反复折腾过折腾到最后发现市面上讲功能权限控制的方案基本就是两大流派RBAC模型和ABAC模型。前者靠角色分包后者靠属性决策各有各的适用场景也各有各的坑。这篇文章把我实际用过的设计思路、表结构、判定流程和踩坑记录都拆开讲一遍。准备做权限模块的新手或者已经在RBAC里被复杂业务逼得想换方案的老人都可以参考。我先给结论RBAC保底ABAC补漏大多数业务系统用“RBAC打底、局部ABAC”的混合方案最稳。1. RBAC模型把功能权限装进角色里1.1 RBAC的核心设计为什么是“角色”RBAC的全称是Role-Based Access Control基于角色的访问控制。它的核心思想其实很好理解不直接把权限绑在用户身上而是先给权限归类打包成“角色”再把角色分配给用户。用户和权限之间隔了一层角色这一层就是整个模型的生命线。为什么中间要隔一层直接给每个用户配权限不是更直接吗我举个现实里的例子公司有一百个客服如果直接给每个人配权限那客服A入职的时候管理员要勾选二十个权限点客服B入职又要勾选二十个操作重复不说只要有一项漏配客服就打电话来投诉“我看不到工单编辑按钮”。但有了角色这个概念管理员只需要创建一个“客服”角色把二十个权限点打包塞进去新员工入职的时候直接把角色挂上去五秒钟完事。这就是RBCA模型最核心的价值权限集中管理、分配动作收敛、避免重复劳动。在功能权限的语境下角色承载的权限对象通常是三类菜单可见性、页面按钮可用性、后端接口可调用性。前两类偏前端展示后一类是真正的安全屏障。预防性的实践经验是不管前端隐藏得多彻底后端接口必须做同样的判定否则别人用手工拼一个接口地址就能绕过限制这在生产环境里是要出大事的。1.2 RBAC数据库表设计与权限编码把RBAC落到数据库最经典的设计是五张表用户表、角色表、权限表、用户角色关联表、角色权限关联表。很多加了第三张关联表的表结构其实是在这两张关系表之上扩展出来的。我给一个可以直接抄的建表思路-- 用户表简化 CREATE TABLE sys_user ( id BIGINT PRIMARY KEY, username VARCHAR(50) UNIQUE, status TINYINT DEFAULT 1 ); -- 角色表 CREATE TABLE sys_role ( id BIGINT PRIMARY KEY, role_name VARCHAR(50), role_code VARCHAR(50) UNIQUE, parent_id BIGINT DEFAULT 0, -- 角色层级预留0为顶级 status TINYINT DEFAULT 1 ); -- 功能权限表 CREATE TABLE sys_permission ( id BIGINT PRIMARY KEY, perm_name VARCHAR(50), perm_code VARCHAR(100) UNIQUE, type TINYINT, -- 1菜单 2按钮 3接口 parent_id BIGINT DEFAULT 0 ); -- 用户-角色关联表 CREATE TABLE sys_user_role ( user_id BIGINT, role_id BIGINT, PRIMARY KEY (user_id, role_id) ); -- 角色-权限关联表 CREATE TABLE sys_role_permission ( role_id BIGINT, permission_id BIGINT, PRIMARY KEY (role_id, permission_id) );这套表结构本身不复杂真正考验功力的是权限编码规范。很多系统用自增id来定位权限后期维护起来简直灾难——没人看得懂id1024到底是什么按钮。我的习惯是采用“模块:动作:范围”三段式编码。比如goods:create:all表示商品模块下创建、范围全部order:audit:dept只允许审核本部门的订单。权限表里每一条记录对应一个功能点前端按钮控制也好后端接口拦截也好都拿这个编码去比对。创建角色、分配权限的时序很简单先创建角色如“客服专员”然后查权限表把该角色需要的权限编码关联进角色权限表最后把用户id和角色id写进用户角色关联表。上线前最好跑一遍“权限完整性校验”把每个角色拥有的权限点列出来让业务负责人确认别等到上线后让真正使用的人自己去试错。边界情况要注意不要在用户表里加一个permission_ids字符串字段。这个设计我很不推荐虽然查询的时候感觉方便了但后续想做统一会话管理、角色级联继承、离职审计的时候你会发现拆解字符串就是一场噩梦。1.3 RBAC的三种升级层级、约束与会话基础版RBAC能解决“员工能看哪些菜单、能点哪些按钮”的问题但真实业务通常要求更高这时候就需要把NIST参照模型里的几个进阶概念拿出来用。第一个是角色层级Role Hierarchy。销售总监应该自动拥有销售主管的所有权限销售主管应该自动拥有销售专员的所有权限如果不用层级就得给总监重复绑定几十个权限点。实现方法不复杂在角色表里加一个parent_id字段分配权限时先把父角色权限复制一遍或者在做权限解析时使用递归向上合并。我用过这两种方式论维护成本的话解析时递归合并更省心因为中间临时新增角色层级时不需要跑同步脚本。第二个是职责分离约束Separation of Duty。财务系统的“制单”和“审核”不能是同一个人仓库管理里的“入库”和“出库”不能让同一角色同时操作。静态职责分离是创建角色时就把这两种角色标记为互斥禁止分配给同一用户动态职责分离则是在一个会话内用户虽然同时拥有两个角色但系统不允许他在同一条业务数据上连续执行两个互斥操作。这个约束能防住明显的内部舞弊风险。第三个是角色会话Session。用户登录后主动选择要激活哪些角色。比如一个人同时是“运营”和“客服主管”登录Web后台时可以只激活“客服主管”那么同一会话内他只能使用管理端功能运营相关的临时权限就不会漏到当前会话里。如果对安全要求更高可以把用户的所有角色授权做成临时撤销类似于灰度发布对照组这在权限审计和账号安全策略里都很有用。2. ABAC模型让规则决定权限而不是角色名称2.1 ABAC的四类属性从哪里来ABAC全称是Attribute-Based Access Control基于属性的访问控制。它的思路和RBAC完全是两个方向RBAC问“你是谁你属于什么角色”ABAC问“当前请求的各方具备哪些属性这些属性组合起来是否满足放行条件”。ABAC定义了四类属性我先列一下主体属性Subject用户的id、部门、职级、工龄、合同类型正式/外包、是否在安全培训白名单里。资源属性Resource被访问对象的类型、归属部门、密级、创建人、负责人、当前状态。动作属性Action查看、创建、编辑、删除、导出、审批、发布。环境属性Environment当前时间、登录IP所属区域、设备类型、网络环境内网/外网、登录方式扫码/密码/SSO。权限判断的逻辑就建立在这四类属性的排列组合上。举个例子外包人员仅在周一至周五的9:00到18:00之间可以“查看”项目文档且只能看自己所在负责项目的“公开”文档正式员工没有时间限制但“删除”文档只允许文档创建人本人执行。这个规则如果用RBAC来表达你得先创建“外包人员”“正式员工”“文档创建人”各种角色还得针对不同项目维度给同一人分不同类型最后角色数量会膨胀到让人崩溃。而ABAC用一段规则就能覆盖这些条件。我在实际项目里更倾向于把ABAC称为“策略式权限”因为它的最小单位不是角色或权限点而是一条一条的访问策略Policy。每一条策略表达的是当前属性组合条件下的“允许/拒绝”。2.2 策略怎么写人话优先代码在后ABAC策略的核心是一系列条件表达式。很多团队会直接用代码库里的if else承担策略逻辑这样写起来最省事但时间久了维护起来很痛苦。我建议把策略表达式独立出来哪怕刚开始只是写成一个配置文件后续也容易做可视化管理和版本控制。用一个我负责过的采购审批场景来演示。采购系统里有一个“创建采购订单”的操作原始RBAC模型规定只有“采购专员”角色能创建需求来了预算金额在5万以上的订单必须同时填写“预算说明”和“供应商比价表”否则禁止提交且所有外包人员创建的订单需要部门主管再次审核。加上这个条件之后如果继续在代码里写if else第二个人来维护的时候根本不敢动这块逻辑。用ABAC策略来表达同一个规则长这样允许创建采购订单 IF 主体.角色 采购专员 AND 资源.金额 50000 允许创建采购订单 IF 主体.角色 采购专员 AND 资源.金额 50000 AND 资源.预算说明已填写 true AND 资源.供应商比价表已填写 true AND 主体.合同类型 正式 允许创建采购订单 IF 主体.角色 采购专员 AND 资源.金额 50000 AND 资源.预算说明已填写 true AND 资源.供应商比价表已填写 true AND 主体.合同类型 外包 AND 需要主管复核 true 默认禁止你看策略本身是可读的业务变了就改策略文件不需要动编译、动部署。正如“允许创建”与“默认禁止”两块之间判定引擎会自动应用“先匹配先执行没匹配到则拒绝”的原则后边我会讲一次请求在ABAC里怎么走。2.3 ABAC请求判定的一次完整流程ABAC在实际系统里的认定过程可以抽象为五个环节。第一步用户发起请求比如提交了一个“删除项目文档”的指令第二步拦截器或网关捕获请求提取主体属性uid、部门、岗位、合同类型第三步根据请求定位资源属性文档id、归属部门、密级、所有者同时采集环境属性当前时间、IP第四步把所有这些属性组成一个上下文对象交给策略决策引擎逐条匹配第五步引擎返回允许/拒绝允许就放行拒绝就返回403并记录日志。这五个环节里最容易被忽略的是第三步的资源属性采集。很多团队实现了主体属性和环境属性但资源属性拿不到策略只能空转。我的经验是资源属性必须在领域模型里预留钩子比如每一个文档表都带上owner_dept、security_level、resource_type这三个字段每次新增资源都要保证这三个字段不回空ABAC才能跑得起来。这个流程和价值密度更高涉及到性能和缓存的优化我在第4章的混合方案里细讲。3. RBAC和ABAC的真实差别与选型3.1 一张表看懂两者的取舍很多人在选型时只看“哪个更先进”我觉得这不对权限模型的本质是管理成本和业务灵活性的博弈。我把两个模型的对比整理成一个表格这样一目了然对比维度RBAC模型ABAC模型权限粒度角色级较粗属性组合级可做到行级/字段级权限定义方式角色绑权限点策略规则匹配属性变更成本改角色权限绑表即可改策略并验证规则不冲突动态适应能力弱新场景要建新角色强改策略配置即可管理负担低角色数量有限高策略数量多且逻辑复杂性能开销低查表就完事高需要属性解析和规则匹配审计追溯清晰知道角色是谁较难需要记录当时的属性快照典型场景后台管理系统、内容管理云平台授权、跨部门数据隔离、风控这张表的信息密度比较高我补充一句RBAC适合人员分工相对固定的系统ABAC适合数据敏感度高、多种条件动态变化的场景。但生产环境往往不是非此即彼大多数写业务系统的公司其实不需要把全部权限都变成属性策略。3.2 按实际场景选方案我把做权限选型归纳成三个问题你的权限维度是不是只有“岗位/角色”这一种你的业务规则里是否经常出现“如果用户是A部门且资源属于B密级且当前是工作日”这种三条件以上的判断你是否需要一个非技术人员也能维护的权限管理界面如果前两个问题回答是“是”第三个回答“很难”那建议以RBAC为基础如果三个问题都反过来那可以更大胆地引入ABAC。但有一条经验我认为很通用权限不是越细越好而是越合适越好。做过一次权限系统之后我对“过度设计”四个字有了新的理解。就比如一个小型内容发布后台用户角色无非编辑、审核、运营、管理员硬要上一套ABAC策略引擎不仅前期开发成本高后期策略一多连自己都维护不动反而容易被新人误改策略导致大面积越权。合理的方式是权限模型要先满足当前业务的确定性需求再为未来留好扩展位。我刚做权限模块的时候总想把所有可能都用上结果一半功能上线半年都没人用到。4. 我的混合方案落地全过程RBAC打底ABAC补漏4.1 基础模型选型和初始化清单我现在做新系统默认先落一套RBAC。第一步是梳理功能清单把所有菜单、按钮、接口列出来每一条都分到业务模块下然后按照“模块:动作:范围”统一编码。第二步是确定角色清单注意这里不是按“权限点倒推角色”而是按公司的岗位职责来定角色比如运营专员、运营主管、财务专员、财务审核、超级管理员每个角色再去勾权限点。第三步就是建表和写初始化脚本。初始化脚本里有一件事不能省给超级管理员角色先做防误删保护。权限系统刚上线的时候最怕操作失误把自己管理员的权限清掉结果整个后台都进不去。我的做法是在代码里硬编码一个角色编码super_admin该角色的权限读取不走数据库缓存直接返回全量权限即便数据库被误改也能保住管理员入口。RBAC打底之后业务方通常会在三个月内提出更细的权限诉求。这时候不要急着推翻RBAC先判断诉求是否能用“角色层级”或“会话约束”解决实在解决不了才把局部功能切换或叠加到ABAC策略上。4.2 在关键业务点叠加ABAC策略混合方案的落地重点在于“在哪里叠加”。我把ABAC策略用在最容易被越权利用的两个点上接口级别的数据范围校验和多条件组合的数据操作校验。以数据范围校验为例RBAC的角色已经决定了用户能访问“项目管理”模块但部门主管只能看本部门项目项目经理本人能看自己所管项目财务人员能看所有项目的预算但看不到技术详情。这种同模块内的行级数据隔离用RBAC显然做不到——总不能为每个部门都创建角色吧。这时候ABAC策略就派上用场允许访问项目列表 IF 主体.角色 部门主管 AND 资源.部门 主体.部门 允许访问项目列表 IF 主体.角色 项目经理 AND 资源.负责人 主体.uid 允许访问项目列表 IF 主体.角色 财务专员 AND 动作.名称 查看预算 AND 资源.密级 内部 默认拒绝这个策略看似复杂落到实现上就是个轻量级规则引擎。我在项目里用的是自己封装的策略决策函数核心逻辑两百多行就够用。比引入外部大型规则引擎更可控出了问题也更好排查。策略存在数据库的rule表线上改策略即时生效不需要发版。每个策略带一个version字段避免多人同时修改互相覆盖这是我吃过亏之后加的。4.3 性能优化权限判定不能拖慢接口权限判定最怕拖慢正常业务接口。RBAC查表很快合并ABAC策略后每请求一次都要做属性聚合和规则匹配性能确实会变差。我采取过三个优化措施实测效果都不错。第一个是分级缓存。用户权限结果按“基本权限角色相关”和“扩展策略属性相关”分开缓存。基本权限缓存5分钟扩展策略的判定结果按资源维度再缓存60秒。比如项目文档这种资源判定结果缓存一分钟完全可接受资源属性变化时主动清理或等待过期都行。第二个是批量决策。列表页一次性返回几十条数据时示例代码里常见的是循环逐条调用权限判断这就是性能灾难。正确做法是先把所有资源的属性字段收集起来一次性调一次批量策略决策拿到一批允许/拒绝集合再组装成前端可显示的数据。这个优化能把列表接口的权限耗时从几十毫秒降到个位数毫秒。第三个是策略索引。不把策略规则全量扫一遍而是先根据动作类型和资源类型粗筛命中部分规则后再做精细属性匹配。因为绝大多数请求只会触及少数几条策略没必要把几百条策略都跑一遍正则。5. 权限系统实战中的坑位记录5.1 RBAC在生产里的三大坑RBAC模型简单但正因为简单很多细节容易被忽略。我实际遇到过的第一个大坑是“角色膨胀”。业务方今天说客服不能看价格明天说客服A组能看华东区价格后天说客服B组能看华南区价格于是角色从“客服”分裂成“客服华东南价格”“客服华西南价格”最后光客服角色就有十几个。这个问题要从两条线解决一是权限编码的科学拆分把区域维度从角色里抽出来放进数据权限规则里二是设立角色审批机制新增角色必须经过技术负责人确认否则走ABAC策略而不是继续建角色。第二个坑是“权限盲从”。给某个角色加了权限之后没有顺手检查其他角色是否也受影响。尤其是在角色层级设计中给父角色加一个权限点所有子角色都跟着有了。所以每次变更角色的权限都要跑一遍“权限变更影响分析”列出受影响角色清单让管理员确认后再发布。第三个坑是“用户角色关联表无限膨胀”。当用户数几十万、角色数几百时user_role表的数据量会很可观连表查询如果不加索引会明显变慢。我给的建议是分两层活跃用户走中间表关联长时间未登录用户落到archive表同时针对user_id和role_id建联合索引权限查询接口指定强制走索引。5.2 ABAC在实战里的三个坑ABAC第一个让我抓狂的问题是“策略不可调试”。基于属性的判断看着简单但策略叠加多了之后一个请求可能同时命中允许和拒绝两条策略不同引擎对冲突的处理方式还不一样。我的习惯是决策引擎里加一个“审计模式”在测试环境把每个请求命中的策略明细全部打在日志里方便定位到底是哪条策略拦住了请求。上线后拒绝操作也会记录策略id排障时直接看策略版本。ABAC第二个坑是“策略复杂度蔓延”。属性可以任意组合规则数量会指数级增长。为了控制复杂度我把策略分成两层全局策略和业务策略。全局策略只有安全管理员能改业务策略由业务系统管理员配置而且业务策略必须绑定到具体的资源类型和动作类型不能存在全模糊匹配的宽策略。这个约束保障了权限系统的整体安全底线。ABAC第三个坑是“规则字段缺失”。属性采集时如果某个资源漏填了所属部门策略里只要有“且资源.部门主体.部门”这条请求就会被默认拒绝。这个设计初期看起来很安全但线上会出现大量莫名其妙的“用户无法访问数据”的工单。我的做法是属性采集时做完整性校验缺失属性不直接判拒绝而是返回“属性缺失”状态由系统主动提示管理员补全资源属性数据。这样才能避免让正常用户承受数据质量的锅。一点实操心得做了这么多权限项目我个人最有体感的结论是不要把权限模型当成一次性的代码任务它更像是业务规则的浓缩层。先按成熟可靠的RBAC把基本骨架拉起来遇到真正“条件组合、动态变化”的业务诉求时再用ABAC策略做局部补位。哪怕是完全从零开始的系统我也不会一口气上全套ABAC复杂度是需要时间消化成本的。权限模块上线当天我还会多做一件事把权限变更日志完整保留下来对应到具体管理员操作和当时的时间节点。这个习惯在后续做安全审计和问题回溯时帮了大忙如果你还没开始做权限审计建议从今天开始补上。
返回列表