一、为什么传统RBAC不够用了?——从打卡平台到Agent平台
讲权限模型之前,先交代一下这个项目的来历。
Workforce Hub 最开始不是我主动要做的。上学期期末设计,我做了一个「员工智能打卡平台」,就是那种前端调摄像头拍照打卡、后端存记录的常规系统。当时觉得做完就完了,没想到这学期开学,老师找到我,问愿不愿意进他的课题组。
老师说我的打卡平台可以继续优化,做好了能当毕设。我一想,那挺好,就开干了。结果越改越收不住——加了排班管理、假期审批、即时消息……项目体量越来越大,早就不止打卡一个功能了。
这时候我去找老师聊,老师说了一句话让我印象很深:"你这个技术栈是传统的堆叠,可以结合当下的 AI 功能,做一些差异化的东西。"然后他推荐我申报学校的大学生创新训练计划,也就是现在的 Workforce Hub。
后面的路由就得自己走了——查资料、问 AI、啃 Spring AI 的文档、研究 Agent 的工作机制。就是在设计 Agent 权限的时候,我遇到了一个绕不过去的问题:
Agent 替用户干活的时候,它能调用哪些能力?能访问哪些数据?
举个例子:员工小张请了一天假,他告诉 Agent"帮我提交请假申请"。这个 Agent 需要:
调用工作流引擎的"创建审批"接口
读取小张的假期余额
查看部门排班信息来判断是否有冲突
但问题来了——这个 Agent 是小张的 Agent,它不能:
直接批准请假(那得部门主管批)
查看其他同事的假期余额(隐私数据)
修改考勤规则(管理员才有的权限)
传统 RBAC 只能回答"用户能做什么",没法回答"Agent 替用户能做什么"。因为 Agent 不是一个用户——它是一个运行中的程序,有自己能力清单,却又受限于启动它的那个用户的授权范围。
这就是我设计三层权限裁剪模型的起点。
二、三层权限裁剪:三个集合求交集
核心思路一句话就能说清楚:
Agent 最终能做的事 = Agent 能力 ∩ 用户授权 ∩ 数据可见域
每次 Agent 执行任务,都动态计算这三个集合的交集。不是一次性配置好的静态权限,而是运行时实时裁剪。
下面逐层拆开讲。
2.1 第一层:Agent 自身的能力边界
每个 Agent 注册时就要声明自己能干什么。在 Workforce Hub 里,Agent 的能力是一组工具(Tool) 的集合:
Agent "员工助理" 的能力清单:
├── workflow:create_approval # 发起审批
├── workflow:query_status # 查询审批状态
├── leave:query_balance # 查假期余额
├── attendance:query_schedule # 查排班信息
├── im:send_message # 发消息
└── kb:search_document # 搜知识库
注意这个 Agent 没有 workflow:approve 和 attendance:update_rule——这些是管理员 Agent 才有的能力。
我的理解:Agent 能力的"最小化原则"。只给它完成本职工作需要的工具,多一个都不给。这样做的好处是:即使后续两层权限出了问题(比如用户授权配错了),Agent 本身的能力天花板也在那里,不会捅出超范围的篓子。
2.2 第二层:用户授权的范围
这就是我们熟悉的 RBAC 部分。在 Workforce Hub 里,用户-角色-权限的关系如下:
当员工小张通过 Agent 发起操作时,系统先查:小张本人有这个权限吗? 如果没有,Agent 也不能替他干。
血泪教训:我一开始写权限校验的时候偷懒,Agent 调用服务时直接绕过用户权限检查,让 Agent 以系统身份执行。结果测试时发现 Agent 能替普通员工查全公司考勤数据——因为服务层没做用户级别的权限校验。后来全部加上了 @PreAuthorize 注解才堵上这个洞。
2.3 第三层:业务数据的可见域
这一层最容易被忽略,但恰恰是 Agent 场景下最关键的。
举个例子:部门主管老王的 Agent 有 attendance:query_report 权限,但这不代表它能查全公司的考勤数据——它只能查老王管辖的部门。
在 Workforce Hub 里,数据可见域的定义包括:
这其实就是 ABAC(基于属性的访问控制) 在 Agent 场景的延伸。不同的是,传统 ABAC 的规则是静态配置的,而 Agent 的数据域需要与另外两层动态交互。
2.4 动态裁剪:运行时求交集
三层权限的关系不是叠加,是求交集。用伪代码表示:
// Agent 每次执行操作时的权限裁剪逻辑
public Set<String> getEffectivePermissions(Agent agent, User user, String action) {
// 第一层:Agent 注册的能力
Set<String> agentCapabilities = agent.getTools();
// 第二层:用户拥有的权限
Set<String> userPermissions = permissionService.getUserPermissions(user);
// 第三层:用户对目标数据的可见域
DataScope dataScope = dataScopeService.getScope(user, action);
// 三层求交集
Set<String> effective = new HashSet<>(agentCapabilities);
effective.retainAll(userPermissions); // ∩ 用户权限
effective.retainAll(dataScope.getAllowed()); // ∩ 数据可见域
return effective; // 最终允许的操作
}
关键要点:这个计算发生在每次 Agent 调用工具时,不是登录时算一次就缓存住。因为用户权限和数据域可能在会话期间发生变化(比如临时授权过期、角色调整)。
三、与传统权限模型的对比
为了做出合适的选择,我在设计阶段对比了几个常见的权限方案:
传统 RBAC 像给每个人发一张通行证,写了能进哪些门。三层裁剪模型像一个保安——每次进门都重新核对你、你的陪同人(Agent)、你今天要进哪个房间,三个条件都满足才放行。
四、实现中的几个关键决策
4.1 权限检查放在哪一层?
这是个让架构师吵翻天的问题。有人说放网关(统一入口,好维护),有人说放 Service 层(贴近业务,灵活)。
我的选择是:两层都放。
网关层:做粗粒度检查(“这个 Agent 有没有被禁用?”),快速拦截
Service 层:做细粒度检查(“这个用户能不能对这个部门的这条数据执行这个操作?”),精确裁剪
// Service 层的典型写法
@PreAuthorize("@permissionEvaluator.canAccess(#request)")
public ApprovalResult createApproval(ApprovalRequest request) {
// 权限校验由 @PreAuthorize 在方法调用前自动完成
// 业务逻辑...
}
4.2 权限规则放代码还是数据库?
这也是一个经典的选择题。我的方案是:
粗粒度规则(角色-权限绑定)→ 放数据库,支持运行时调整
细粒度规则(数据域、字段级)→ 放代码 + 配置文件,因为和业务逻辑耦合太紧,做成配置项反而容易出错
说实话,这不是什么创新做法,算是一个中规中矩但稳定可靠的选择。
4.3 权限变更的实时生效
如果管理员临时撤销了某个用户的审批权限,Agent 的下一次工具调用就应该立即感知到,不能等到用户重新登录。
Workforce Hub 的做法是:权限查询不走缓存,每次调用都实时查库。代价是多了几次数据库查询,但换来了权限变更的秒级生效。对于企业协同场景,这个取舍是值得的。
五、总结
核心收获:
Agent 权限不是用户权限的简单复制——它多了一个"能力边界"维度,必须独立建模
三层裁剪的本质是运行时动态求交集,而不是静态配置一张权限表
数据域(第三层)是最容易被忽略但恰恰最关键的——它决定了 Agent 能看到什么、操作什么
权限检查要分层:网关做粗筛、Service 做精细裁剪,两者缺一不可
下一步计划:
完善 Agent 工具的风险等级分级(只读/写入/管理),不同风险等级有不同的审批策略
引入权限变更审计日志,记录每次权限裁剪的计算过程
对接 Spring AI 的 ToolCallback 机制,让 Agent 自动感知权限变更
个人感悟:权限模型设计这事,如果只是照着 RBAC 建几张表确实不难——但一旦系统里有了 Agent,问题就从"用户能做什么"变成了"Agent 替用户能做什么",多了一个维度,复杂度直接翻倍。这个过程让我意识到,做系统设计不能只套模板,得从业务场景倒推,想清楚了再动手。
默认评论
Halo系统提供的评论