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

你想想看,如果只用角色名来区分,客服主管和财务都是「高级角色」,但他们对订单的可见范围完全不同。所以资源级权限必须跟 数据范围过滤器 配合使用。

我的经验: 资源级权限的「范围」字段,我建议用枚举值 + 自定义表达式。枚举值覆盖常见场景(own、all、dept),自定义表达式应对奇葩需求(比如「只看金额大于 10000 的订单」)。别问我为什么知道——我曾经被一个「只看上个月华南区已发货订单」的需求折磨了两周。

4.2 字段级权限控制

字段级权限,比资源级更细。它控制的是「同一个资源里,哪些字段你能看,哪些字段你能改」。

我记得有个真实案例:某 CRM 系统里,销售能看到客户的手机号,但运营只能看到客户姓名和邮箱。如果只做资源级控制,要么给运营全部字段,要么什么都不给——都不对。这时候就需要字段级权限。

字段级权限的实现,我总结为三种模式:

  1. 白名单模式:显式声明哪些字段可见。适合敏感字段少的场景。
  2. 黑名单模式:声明哪些字段隐藏。适合大部分字段公开的场景。
  3. 表达式模式:根据条件动态决定字段可见性。比如「金额大于 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;
  }, {});
}
注意: 字段级权限不能只在后端做一次过滤就完事。前端也要配合隐藏对应的输入框和显示区域。否则用户虽然提交不了,但能看到 DOM 里的隐藏字段——这属于信息泄露。我曾经审计过一个系统,后端字段权限做得很好,但前端直接把所有字段渲染在页面上然后用 CSS 隐藏……嗯,你懂的。

4.3 权限继承与覆盖

权限继承,是解决「角色多了怎么管理」的关键机制。说白了,就是让子角色自动拥有父角色的权限,然后按需调整。

我见过最糟糕的做法:每个角色独立配置所有权限。结果 50 个角色,每个角色配 200 项权限,改一个公共权限要遍历所有角色。后来我强制推行了继承机制,配置量直接降了 70%。

权限继承的典型模型:

  • 角色继承:子角色继承父角色的全部权限。比如「高级客服」继承「初级客服」的所有权限,再加一个「退款审批」权限。
  • 组织继承:下级组织继承上级组织的权限模板。比如「上海分公司」继承「总公司」的权限基线,再加本地特色权限。
  • 用户继承:用户继承其所属角色的权限。这是最基础的。

但继承有个大问题:覆盖。子角色想去掉父角色的某个权限怎么办?

我常用的策略是「正向继承 + 负向覆盖」:

角色 继承自 新增权限 移除权限
初级客服 - 查看订单、修改备注 -
高级客服 初级客服 退款审批、查看客户手机号 -
客服主管 高级客服 查看全部订单、导出数据 修改备注(主管不需要改备注)

你看,客服主管继承了高级客服的所有权限,但把「修改备注」给移除了。这种「继承 + 覆盖」的模型,在 RBAC 扩展里叫 HRBAC(Hierarchical RBAC)。

实现建议: 权限覆盖的优先级要明确。我一般定三条规则:1)显式拒绝 > 显式允许 > 继承允许;2)用户级权限 > 角色级权限 > 组织级权限;3)覆盖只对当前节点生效,不影响兄弟节点。这三条规则能解决 90% 的权限冲突。

4.4 细粒度权限的架构设计

讲完了概念,咱们看看实际怎么落地。我画了一张细粒度权限的架构图,你看完就明白了:

细粒度权限控制架构图 细粒度权限控制架构 用户 / 请求 身份认证 授权决策引擎 资源级权限 API + 资源实例 + 操作 字段级权限 白名单 / 黑名单 / 表达式 继承与覆盖 HRBAC 模型 最终权限结果 三层校验顺序:身份认证 → 资源级 → 字段级 → 继承覆盖合并 → 输出最终权限

这张图的核心逻辑是:请求进来,先过身份认证,再过授权决策引擎。引擎会同时检查资源级、字段级和继承覆盖规则,最后合并输出最终权限。每一步都是「与」关系——只要有一层拒绝,请求就被拦截。

4.5 实战中的避坑指南

做细粒度权限,有几个坑我替你们踩过了,直接说:

  • 别把权限逻辑写死在代码里。我见过有人用 if-else 判断角色名来隐藏字段,结果每次加角色都要改代码、发版。权限应该是配置化的,存在数据库或配置中心里。
  • 缓存权限要谨慎。权限变更后,缓存没刷新,用户还能用旧权限操作。我建议用「版本号 + 主动失效」策略,每次权限变更递增版本号,用户请求时校验版本号是否一致。
  • 字段级权限别做太深。嵌套对象的字段权限(比如 order.user.address.city)实现起来非常复杂。我一般限制最多两级,超过两级就用扁平化处理。
  • 权限覆盖要有日志。我曾经排查过一个权限 bug,用户说「我明明有权限,为什么报 403?」查了半天,发现是组织级权限覆盖了角色级权限。从那以后,每次权限覆盖我都要求打日志,方便回溯。
重要提醒: 细粒度权限虽然灵活,但不要过度设计。如果你的系统只有 3 个角色、10 个接口,粗粒度完全够用。细粒度是给复杂业务准备的,别为了「炫技」把简单问题复杂化。我见过一个 5 人团队的项目,做了 4 层权限继承,最后没人能说清楚某个用户到底有什么权限——这就是过度设计的典型症状。

好了,这一章的内容就这些。细粒度权限控制,说白了就是「把权限切得足够细,细到能精确匹配业务需求」。资源级控制你能访问什么,字段级控制你能看到什么,继承与覆盖解决权限怎么复用和调整。这三板斧用好了,大部分权限场景都能覆盖。