4. 权限粒度设计:粗粒度与细粒度权限、行级与列级权限、API端点权限、数据脱敏与权限结合

权限粒度,说白了就是「你在哪个层级上做控制」。

我早年接手过一个内部数据平台,权限设计就两档:管理员和普通用户。普通用户能看到所有订单数据,包括金额、手机号、地址。销售团队觉得太危险,技术团队觉得改起来太麻烦。两边僵持了两个月,最后出了个数据泄露事件才被迫重构。

嗯,从那以后我养成了一个习惯:在设计阶段就把权限粒度想清楚,而不是等出事了再补。

4.1 粗粒度 vs 细粒度:先选对方向

粗粒度权限,就是「一刀切」。比如:

  • 用户属于「销售角色」,就能访问所有客户数据
  • 用户属于「财务角色」,就能访问所有发票数据

细粒度权限,则是「切得更碎」。比如:

  • 用户只能访问自己负责的客户
  • 用户只能查看金额小于10万的订单
  • 用户只能在9:00-18:00之间访问数据
维度 粗粒度 细粒度
管理成本 低,配置简单 高,需要精细维护
灵活性 差,容易「一刀切」 强,能应对复杂场景
性能影响 小,规则少 大,每次请求都要计算
安全等级 低,容易越权 高,边界清晰
适用场景 内部工具、低风险数据 对外API、敏感数据、合规场景
我的建议:不要一开始就追求极致细粒度。先粗后细,逐步迭代。我见过太多团队在细粒度上花了三个月,结果业务需求变了,全部白做。

4.2 行级与列级权限:数据库层面的「手术刀」

行级权限,控制的是「你能看到哪些记录」。列级权限,控制的是「你能看到哪些字段」。这两个经常一起用。

行级权限

举个例子,一个CRM系统:

  • 销售A只能看到自己名下的客户
  • 销售经理能看到整个团队的客户
  • 财务只能看到「已成交」状态的客户

实现方式有很多种。我比较推荐的是策略模式 + 查询拦截:

-- 伪代码:行级权限过滤
SELECT * FROM customers
WHERE tenant_id = current_user.tenant_id
  AND (owner_id = current_user.id OR current_user.role = 'manager')
  AND (status != 'archived' OR current_user.role = 'admin');
注意:行级权限最容易踩的坑是「权限组合爆炸」。我曾经在一个项目中,行级规则叠加了5层,结果一个查询要解析200多行权限SQL,性能直接崩了。后来我们引入了权限缓存 + 预计算才解决。

列级权限

列级权限控制的是字段级别的可见性。比如:

  • 客服能看到用户的手机号,但不能看到身份证号
  • 运营能看到用户的邮箱,但不能看到支付记录
  • 审计能看到所有字段,但只能看不能改

实现上,我习惯在ORM层做拦截:

// 伪代码:列级权限过滤
public List<String> getAllowedColumns(String userId, String tableName) {
    // 从权限中心获取用户对该表的可访问列
    return permissionService.getAllowedColumns(userId, tableName);
}

// 查询时动态拼接
String sql = "SELECT " + String.join(",", allowedColumns) + " FROM customers WHERE ...";
核心原则:行级权限控制「能不能看到这条数据」,列级权限控制「能看到这条数据的哪些部分」。两者是与的关系——必须同时满足。

4.3 API端点权限:把守好每一道门

API端点权限,就是控制「谁能调用哪个接口」。这是微服务架构下的标配。

我习惯把API权限分为三个层级:

  1. 服务级:这个微服务是否对调用方开放
  2. 端点级:这个具体的API端点是否允许调用
  3. 参数级:调用时传入的参数是否在允许范围内

举个例子,一个订单服务的API设计:

API端点 允许角色 参数限制
GET /orders 销售、客服、财务 只能查自己相关的订单
GET /orders/{id} 销售、客服、财务、管理员 管理员可查全部,其他人只能查自己相关的
POST /orders 销售 金额不能超过10万
DELETE /orders/{id} 管理员 只能删除「已取消」状态的订单
避坑指南:我曾经在API网关层只做了端点级校验,没做参数级校验。结果一个销售通过修改请求参数,查到了全公司的订单数据。从那以后,我坚持网关层做粗粒度校验,业务层做细粒度校验,两层都过才算通过。

4.4 数据脱敏与权限结合:既要看见,又要看不见

数据脱敏,说白了就是「给你看,但不给你看全」。比如:

  • 手机号显示为 138****1234
  • 身份证号显示为 110101********1234
  • 银行卡号显示为 6222****1234

脱敏和权限怎么结合?我总结了一个三级脱敏模型:

级别 说明 示例 适用角色
L1 - 明文 完整展示原始数据 13812341234 管理员、合规审计
L2 - 部分脱敏 保留部分信息用于识别 138****1234 客服、销售
L3 - 完全脱敏 只保留类型标识 手机号(已脱敏) 运营、数据分析

实现上,我推荐在数据出口层做脱敏,而不是在数据库层。原因很简单:

  • 数据库层脱敏,会影响所有查询,包括需要明文的场景
  • 出口层脱敏,可以针对不同角色动态调整脱敏规则
// 伪代码:出口层动态脱敏
public String maskPhoneNumber(String phone, User user) {
    if (user.hasPermission("PHONE_FULL_ACCESS")) {
        return phone; // 明文
    } else if (user.hasPermission("PHONE_PARTIAL_ACCESS")) {
        return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2"); // 部分脱敏
    } else {
        return "手机号(已脱敏)"; // 完全脱敏
    }
}
重要提醒:脱敏不是万能的。如果用户有「导出」权限,脱敏后的数据导出后依然存在泄露风险。我建议脱敏 + 水印 + 操作审计三管齐下。
权限粒度设计知识体系 权限粒度设计 粗粒度 vs 细粒度 行级与列级权限 API端点权限 数据脱敏与权限 粗粒度 vs 细粒度 行级与列级权限 API端点权限 数据脱敏与权限 角色/部门级 功能模块级 行级:记录过滤 列级:字段屏蔽 服务级/端点级 参数级校验 动态脱敏策略 出口层脱敏 核心原则:最小权限 + 按需分配 粗粒度兜底,细粒度补位,脱敏保底线

最后说一句:权限粒度设计没有银弹。你想想看,一个只有10个人的创业团队,搞行级列级权限就是过度设计。但一个千人规模的金融平台,粗粒度权限就是定时炸弹。

我的经验是:先画清楚数据流图,再决定在哪里切权限。数据流到哪里,权限就控制到哪里。别为了设计而设计,也别为了省事而一刀切。