4、角色与权限分配:细粒度权限控制
权限管理做到一定程度,你会发现一个尴尬的局面:角色倒是分好了,但用户一多,需求一杂,粗粒度的角色根本兜不住。比如「运营总监」和「运营专员」都是运营角色,但总监能看所有客户的合同金额,专员只能看客户名字——这种差异,靠一个「运营」角色是搞不定的。
这就是我们今天要聊的:细粒度权限控制。说白了,就是把权限切得更碎,碎到资源级别、字段级别,甚至行级别。我这些年做权限系统,踩得最深的坑就是一开始图省事用了粗粒度,后来改得想哭。所以这一章,咱们一次性把细粒度控制、权限继承与覆盖讲透。
4.1 资源级权限控制
资源级权限,就是控制「用户能不能访问某个具体资源」。比如一个订单管理系统,订单就是资源。普通客服只能看自己的订单,客服主管能看所有订单,财务能看到已付款的订单——这就是资源级控制。
我习惯把资源级权限分为三层:
- API 端点层:能不能访问某个接口。比如
/api/orders只有管理员能调。 - 资源实例层:能不能操作某个具体数据。比如订单 ID=10086,只有归属人能看到。
- 操作类型层:能对这个资源做什么。读、写、删、审批,各算一种。
举个例子,一个典型的资源权限配置表长这样:
| 角色 | 资源路径 | 操作 | 范围 |
|---|---|---|---|
| 客服专员 | /api/orders | GET | own(仅自己) |
| 客服主管 | /api/orders | GET | all(全部) |
| 财务 | /api/orders | GET | paid(已付款) |
| 管理员 | /api/orders | GET, POST, DELETE | all |
你想想看,如果只用角色名来区分,客服主管和财务都是「高级角色」,但他们对订单的可见范围完全不同。所以资源级权限必须跟 数据范围过滤器 配合使用。
4.2 字段级权限控制
字段级权限,比资源级更细。它控制的是「同一个资源里,哪些字段你能看,哪些字段你能改」。
我记得有个真实案例:某 CRM 系统里,销售能看到客户的手机号,但运营只能看到客户姓名和邮箱。如果只做资源级控制,要么给运营全部字段,要么什么都不给——都不对。这时候就需要字段级权限。
字段级权限的实现,我总结为三种模式:
- 白名单模式:显式声明哪些字段可见。适合敏感字段少的场景。
- 黑名单模式:声明哪些字段隐藏。适合大部分字段公开的场景。
- 表达式模式:根据条件动态决定字段可见性。比如「金额大于 5000 时隐藏客户名」。
来看一个字段权限配置的伪代码:
// 字段权限配置示例
{
"role": "运营专员",
"resource": "customer",
"fieldPermissions": {
"name": "read",
"email": "read",
"phone": "none", // 不可见
"address": "read",
"creditScore": "none" // 不可见
}
}
// 权限校验逻辑
function filterFields(user, resource, data) {
const permissions = getFieldPermissions(user.role, resource);
return Object.keys(data).reduce((result, field) => {
if (permissions[field] && permissions[field] !== 'none') {
result[field] = data[field];
}
return result;
}, {});
}
4.3 权限继承与覆盖
权限继承,是解决「角色多了怎么管理」的关键机制。说白了,就是让子角色自动拥有父角色的权限,然后按需调整。
我见过最糟糕的做法:每个角色独立配置所有权限。结果 50 个角色,每个角色配 200 项权限,改一个公共权限要遍历所有角色。后来我强制推行了继承机制,配置量直接降了 70%。
权限继承的典型模型:
- 角色继承:子角色继承父角色的全部权限。比如「高级客服」继承「初级客服」的所有权限,再加一个「退款审批」权限。
- 组织继承:下级组织继承上级组织的权限模板。比如「上海分公司」继承「总公司」的权限基线,再加本地特色权限。
- 用户继承:用户继承其所属角色的权限。这是最基础的。
但继承有个大问题:覆盖。子角色想去掉父角色的某个权限怎么办?
我常用的策略是「正向继承 + 负向覆盖」:
| 角色 | 继承自 | 新增权限 | 移除权限 |
|---|---|---|---|
| 初级客服 | - | 查看订单、修改备注 | - |
| 高级客服 | 初级客服 | 退款审批、查看客户手机号 | - |
| 客服主管 | 高级客服 | 查看全部订单、导出数据 | 修改备注(主管不需要改备注) |
你看,客服主管继承了高级客服的所有权限,但把「修改备注」给移除了。这种「继承 + 覆盖」的模型,在 RBAC 扩展里叫 HRBAC(Hierarchical RBAC)。
4.4 细粒度权限的架构设计
讲完了概念,咱们看看实际怎么落地。我画了一张细粒度权限的架构图,你看完就明白了:
这张图的核心逻辑是:请求进来,先过身份认证,再过授权决策引擎。引擎会同时检查资源级、字段级和继承覆盖规则,最后合并输出最终权限。每一步都是「与」关系——只要有一层拒绝,请求就被拦截。
4.5 实战中的避坑指南
做细粒度权限,有几个坑我替你们踩过了,直接说:
- 别把权限逻辑写死在代码里。我见过有人用 if-else 判断角色名来隐藏字段,结果每次加角色都要改代码、发版。权限应该是配置化的,存在数据库或配置中心里。
- 缓存权限要谨慎。权限变更后,缓存没刷新,用户还能用旧权限操作。我建议用「版本号 + 主动失效」策略,每次权限变更递增版本号,用户请求时校验版本号是否一致。
- 字段级权限别做太深。嵌套对象的字段权限(比如 order.user.address.city)实现起来非常复杂。我一般限制最多两级,超过两级就用扁平化处理。
- 权限覆盖要有日志。我曾经排查过一个权限 bug,用户说「我明明有权限,为什么报 403?」查了半天,发现是组织级权限覆盖了角色级权限。从那以后,每次权限覆盖我都要求打日志,方便回溯。
好了,这一章的内容就这些。细粒度权限控制,说白了就是「把权限切得足够细,细到能精确匹配业务需求」。资源级控制你能访问什么,字段级控制你能看到什么,继承与覆盖解决权限怎么复用和调整。这三板斧用好了,大部分权限场景都能覆盖。