企业级权限系统怎么设计三 —— 数据权限控制
前两篇文章,我们主要梳理了权限系统中的功能权限的设计。功能权限解决了“用户能做什么操作”的问题,但企业应用中还有另外一种权限:用户能对哪些数据进行这些操作?即数据权限。
一个典型的场景是:销售部的张三和李四都能使用“查看订单”的功能,但张三可能只能看自己负责的订单,而李四作为经理,可以看到本部门所有销售的订单。这就是数据权限在起作用。
注:本文不仅总结了常见的数据权限设计方案,也整理了更复杂但尚未完全落地的模型,供未来扩展和复杂场景参考。
数据权限的构成:行级权限与列级权限
数据权限一般包含两大维度:行级权限和列级权限。
- 行级权限控制“哪些记录”可见,比如某用户只能查看自己的订单、本部门的订单或指定部门的数据等。
- 列级权限控制“哪些字段”可见,比如一个用户只能看到客户表中的姓名和联系方式,其他敏感字段(如身份证号、收入)不可见,或需要脱敏处理。
可以理解为:行级权限决定数据的范围,列级权限决定访问的数据内容(字段)。
实现方案的演进
下边我们逐步用更加灵活的模型来支持不同复杂度的业务,当然越灵活的模型带来运维成本,技术复杂度也在逐步提升,在实际场景中请合理选择。
方式一:硬编码 —— 适用简单场景
最直接的做法是在 SQL 或代码中写死限制逻辑。
-- 仅查看本部门及下属部门数据的示例
-- 查询仅查看自己数据
SELECT * FROM orders WHERE user_id = #{currentUserId};
-- 查询本部门及下级部门数据,部门ID集合作为参数传入
SELECT * FROM orders WHERE dept_id IN (#{deptIdList});
- 优点: 快速实现,适合固定规则。
- 缺点: 灵活性差,无法动态配置,维护成本高。
方式二:RBAC + 数据范围 —— 覆盖主流归属场景
在权限点绑定的基础上,加上数据范围的控制。
表结构示例:
role_id | permission_code | data_scope |
---|---|---|
r001 | TRANSFER_QUERY | DEPT_AND_BELOW |
r002 | LOAN_APPROVE | SELF |
r003 | CUSTOMER_QUERY | CUSTOM_DEPT(dept1, dept2) |
data_scope 字段常见取值:
SELF
:仅本人数据DEPT
:本部门数据DEPT_AND_BELOW
:本部门及下级部门ALL
:所有数据CUSTOM_DEPT(dept1, dept2)
:指定多个部门
方式三:策略表达式模式 —— 表达更复杂的数据规则
当范围控制不够用时,可以用策略表达式表述更灵活的条件,比如金额阈值、业务状态、地区等。
示例表结构:
erDiagram
DATA_POLICY_BINDING {
varchar subject_type "主体类型 (user/role)"
varchar subject_id "主体ID"
int permission_point_id "权限点id"
int policy_id FK "关联的策略ID"
}
DATA_POLICY {
int id PK
varchar policy_code "策略代码,便于配置和识别"
varchar pre_condition_expr "策略前置条件表达式"
boolean enabled "策略是否启用"
}
DATA_POLICY_GROUP {
int id PK
int policy_id FK "关联的策略ID"
varchar group_code "条件组代码,便于内部识别"
varchar name "条件组名称,便于后台维护"
varchar logical_op "组内条件的逻辑操作 (AND/OR)"
}
DATA_POLICY_CONDITION {
int id PK
int group_id FK "关联的条件组ID"
varchar field "条件字段"
varchar operator "条件操作符"
varchar value_type "值类型 (const/userAttr)"
varchar value_expr "值表达式"
}
DATA_POLICY ||--|{ DATA_POLICY_BINDING : "被绑定到主体"
DATA_POLICY ||--|{ DATA_POLICY_GROUP : "包含条件组"
DATA_POLICY_GROUP ||--|{ DATA_POLICY_CONDITION : "包含条件"
示例数据:> 表达金额大于 50 万,且(状态为已审批 或 区域为华北)
为便于区分,例子中的id我适用了字符串
DATA_POLICY_BINDING
角色r001在权限点5001绑定了策略p001
subject_type | subject_id | permission_point_id | policy_id |
---|---|---|---|
role | r001 | 5001 | p001 |
DATA_POLICY
策略p001是一个高金额审批策略,其中前置条件pre_condition_expr可提前判断不需要sql拼装是策略的前置过滤条件,一般用于短路判断,如果不满足就直接拒绝
policy_id | policy_code | policy_name | pre_condition_expr | enabled |
---|---|---|---|---|
p001 | HIGH_AMOUNT_APPROVAL | 高金额审批策略 | true |
DATA_POLICY_GROUP
策略p001有两个条件组g001和g002,条件组g001如果配置多个条件则为AND关系,条件组g002如果有多个条件则为OR关系
id | policy_id | group_code | name | logical_op |
---|---|---|---|---|
g001 | p001 | GRP_AMOUNT_CHECK | 金额检查组 | AND |
g002 | p001 | GRP_STATUS_REGION | 状态或地区组 | OR |
DATA_POLICY_CONDITION
条件组g001只有一个条件为amount>500000,条件g002有两个条件状态为approved,或者区域为华北
id | group_id | field | operator | value_type | value_expr |
---|---|---|---|---|---|
1 | g001 | amount | const | 500000 | |
2 | g002 | status | = | const | approved |
3 | g002 | region | = | const | 华北 |
方式四:ABAC 模型 —— 适用于复杂/平台级系统
ABAC(Attribute-Based Access Control)模型,从“属性”出发控制访问,属性可来自用户、资源或环境,适用于复杂动态的权限决策(既能控制功能权限,也能控制数据权限)。
ABAC 模型强调策略与主体解耦,通常采用如下结构:
示例表结构
erDiagram
ABAC_BINDING {
varchar subject_type "主体类型 (user/role)"
varchar subject_id "主体ID"
int policy_id FK "关联的ABAC策略ID"
}
ABAC_POLICY {
int policy_id PK
varchar policy_code "策略代码,便于配置和识别"
varchar policy_name "策略名称"
varchar resource "资源标识"
varchar action "操作标识"
varchar pre_condition_expr "策略前置条件表达式"
boolean enabled "策略是否启用"
}
ABAC_POLICY_GROUP {
int id PK
int policy_id FK "关联的ABAC策略ID"
varchar group_code "条件组代码,便于内部识别"
varchar name "条件组名称,便于后台维护"
varchar logical_op "组内条件的逻辑操作 (AND/OR)"
}
ABAC_POLICY_CONDITION {
int id PK
int group_id FK
varchar field "条件字段"
varchar operator "条件操作符"
varchar value_type "值类型 (const/userAttr)"
varchar value_expr "值表达式"
}
ABAC_POLICY ||--|{ ABAC_BINDING : "被绑定到主体"
ABAC_POLICY ||--|{ ABAC_POLICY_GROUP : "包含条件组"
ABAC_POLICY_GROUP ||--|{ ABAC_POLICY_CONDITION : "包含条件"
示例数据:> 表达金额大于 50 万,且(状态为已审批 或 区域为华北)
ABAC_BINDING
用户u001和角色r001分别都绑定了策略p001
subject_type | subject_id | policy_id |
---|---|---|
user | u001 | p001 |
role | r001 | p001 |
ABAC_POLICY
策略p001针对资源loan的approve生效,其中前置条件pre_condition_expr可提前判断不需要sql拼装
policy_id | policy_code | policy_name | resource | action | pre_condition_expr | enabled |
---|---|---|---|---|---|---|
p001 | HIGH_AMOUNT_APPROVAL | 高金额审批策略 | loan | approve | true |
ABAC_POLICY_GROUP
策略p001有两个条件组g001和g002,条件组g001如果配置多个条件则为AND关系,条件组g002如果有多个条件则为OR关系
id | policy_id | group_code | name | logical_op |
---|---|---|---|---|
g001 | p001 | GRP_AMOUNT_CHECK | 金额检查组 | AND |
g002 | p001 | GRP_STATUS_REGION | 状态或地区组 | OR |
ABAC_POLICY_CONDITION
条件组g001只有一个条件为amount>500000,条件g002有两个条件状态为approved,或者区域为华北
id | group_id | field | operator | value_type | value_expr |
---|---|---|---|---|---|
1 | g001 | amount | const | 500000 | |
2 | g002 | status | = | const | approved |
3 | g002 | region | = | const | 华北 |
为什么需要 resource 和 action 字段?
resource
标识策略适用的资源对象,比如order
、loan
、customer
等;action
标识操作行为,比如view
(查看)、edit
(编辑)、approve
(审批)等;
通过资源 + 动作联合定位,可以让一条策略精准适配对应的操作上下文,避免因资源或动作不同导致误匹配。
同时也方便后续支持不同资源下的统一权限管理。
为什么需要 ABAC_BINDING 表?
- 性能考虑:通过绑定用户或角色,快速缩小策略检索范围,避免海量策略全表扫描。
- 灵活配置:既可以针对单个用户授权,也可以针对角色、组织授权,适配不同组织模型。
- 管理方便:权限配置界面可以更清晰地展现策略与用户/角色的对应关系。