# 对象权限控制
平台用 角色要求(RoleRequirement)控制谁可以查看、创建、修改、删除某类对象,以及谁可以访问某个表单、动作、菜单。需要根据对象本身的字段值做判断时,可以在角色要求上挂一段自定义逻辑。
提示
1.0 起已移除基于 Request Map 的权限配置,以及表单上的 enable roles 字段,统一改用本页介绍的角色要求。
# 基础权限控制
# 角色要求
一个角色要求(RoleRequirement)包含以下字段:
| 属性名 | 类型 | 说明 |
|---|---|---|
name | String | 名称,租户内唯一,创建后不可修改 |
hasPermissionRoles | List<HierarchyRole> | 满足要求的角色列表,用户拥有其中任意一个角色即满足 |
customLogic | DynamicLogic | 自定义判断逻辑,可以为空,见 动态权限 |
判断规则:
- 设置了
customLogic时,只按自定义逻辑的结果判断,hasPermissionRoles不再起作用; - 否则判断用户是否拥有
hasPermissionRoles中的任一角色。角色可以继承,例如平台内置的ROLE_ADMIN包含ROLE_DEVELOPER,ROLE_DEVELOPER包含ROLE_USER,所以拥有ROLE_ADMIN的用户也满足只要求ROLE_USER的角色要求。
平台内置三个角色要求:
| 名称 | 满足要求的角色 |
|---|---|
USER | ROLE_USER |
DEVELOPER | ROLE_DEVELOPER |
ADMIN | ROLE_ADMIN |
# 领域模型的增删改查权限
每个领域模型(DomainClass)上有四个角色要求字段,分别控制四种操作:
| 属性名 | 控制的操作 |
|---|---|
createRoleRequirement | 创建 |
readRoleRequirement | 查看 |
updateRoleRequirement | 修改 |
deleteRoleRequirement | 删除 |
注意
某个操作没有配置角色要求时,任何人(包括管理员)都不能执行该操作。例如领域模型没有配置 readRoleRequirement,通过 /data/<领域模型> 查询会返回 403 NoPermission。
这四个字段在调用平台的数据接口(/data/...)时由后端校验,也决定了界面上是否显示新建、修改、删除按钮。
# 表单、动作、菜单的访问权限
表单(DynamicForm)、对象动作(DynamicAction)、菜单(DynamicMenu)上各有一个 accessRequirement(访问要求)字段,未配置时的行为不同:
| 对象 | 配置了 accessRequirement | 未配置 |
|---|---|---|
| 表单 | 满足要求才能打开和读取该表单 | 不做额外限制(仍需登录,并受领域模型查看权限约束) |
| 菜单 | 满足要求才显示该菜单 | 表单类菜单:显示(仍受所指向表单的要求限制);分组菜单、链接菜单:不显示 |
| 对象动作 | 满足要求才显示、才能执行 | 任何人都不能执行 |
# 通过种子数据配置
当前版本的界面上没有编辑角色要求的入口,领域模型、表单、动作、菜单的表单里也没有这些字段。请在插件或种子数据的 CSV 中配置:
RoleRequirement.csv:定义角色要求。列名 hasPermissionRoles.name[:] 中的 [:] 表示多个角色之间用英文冒号 : 分隔,写成 [ROLE_A:ROLE_B],满足其中任意一个角色即可。不要用逗号写成 [ROLE_A,ROLE_B],逗号是 CSV 的列分隔符,会把这一行拆坏。
name(*),hasPermissionRoles.name[:],customLogic.name
EQUIPMENT_ADMIN,ROLE_EQUIPMENT_ADMIN,
REPORT_VIEWER,[ROLE_USER],
EQUIPMENT_MANAGER,[ROLE_EQUIPMENT_ADMIN:ROLE_ADMIN],
2
3
4
5
其中 ROLE_EQUIPMENT_ADMIN 不是平台内置角色,要先在 HierarchyRole.csv 里定义(例如 ROLE_EQUIPMENT_ADMIN,[ROLE_USER]),写法见 数据导入。
DomainClass*.csv:给领域模型指定四种操作的角色要求。
shortName(*),extInfo,createRoleRequirement.name,readRoleRequirement.name,updateRoleRequirement.name,deleteRoleRequirement.name
EquipmentProfile,,EQUIPMENT_ADMIN,USER,EQUIPMENT_ADMIN,EQUIPMENT_ADMIN
2
3
DynamicForm*.csv、DynamicMenu*.csv、DynamicAction*.csv:用 accessRequirement.name 列指定访问要求,例如
name(*),label,parent.name,icon,link,type,displaySequence,form.name,accessRequirement.name
CSV 的通用写法见 数据导入。
# 动态权限
需要根据对象的字段值、当前用户等信息动态判断权限时,给角色要求设置 customLogic。
# 自定义逻辑的注入变量
| 变量名称 | 变量类型 | 描述 |
|---|---|---|
object | Object | 被检查的对象,随使用场景不同,见下方说明 |
hasPermissionRoles | List<HierarchyRole> | 该角色要求上配置的角色列表 |
user | tech.muyan.api.security.MuyanAuthentication | 当前用户,user.authorities*.authority 可取得角色名列表 |
逻辑返回 [result: true] 表示满足要求,返回 [result: false] 或不返回 result 表示不满足。
示例:只有开发者可以修改 Webhook,但名为“接收订单通知”的 Webhook 谁都不能改。
boolean isDeveloper = user.authorities*.authority.contains('ROLE_DEVELOPER')
return [result: isDeveloper && object?.name != '接收订单通知']
2
object 的取值:
| 角色要求用在 | object |
|---|---|
表单 / 动作 / 菜单的 accessRequirement | 该表单(DynamicForm)/ 动作(DynamicAction)/ 菜单(DynamicMenu) |
自定义 Controller 的 roleRequirement | 当前请求(HttpServletRequest) |
| 领域模型的四个角色要求 | 见下方说明,可能是具体对象,也可能是 null |
领域模型角色要求中的 object
object 只有在按对象查询权限时(即下文的 权限查询接口 POST /permissions/<领域模型>/,前端据此决定每一行是否显示修改、删除按钮)才是具体的对象。后端在处理 /data/... 增删改查请求时,检查的是“能否操作这类对象”,传入的 object 为 null。
实测:用上面的示例逻辑作为 DynamicWebhook 的 updateRoleRequirement,权限查询接口对“接收订单通知”返回 update: false,界面上不显示修改按钮;但直接调用 PUT /api/data/DynamicWebhook/<id> 仍然修改成功。所以对象级的判断目前只能控制界面,不能作为数据安全的保证;逻辑也必须能处理 object == null 的情况,否则所有增删改查请求都会被拒绝。
# 动态创建权限
除了在 createRoleRequirement 上挂自定义逻辑,还可以为领域模型创建一个 Create ability(CREATE)类型的对象钩子,决定当前用户能否创建该类对象。对象钩子在 开发定制 > 对象客制化 中维护,新建时这样填:
| 字段 | 填写 |
|---|---|
| 名称 | 钩子的名称,必填,创建后不可修改 |
| 类型 | Create ability(选项当前显示为英文) |
| 关联对象类型 | 该逻辑适用的领域模型 |
| 核心逻辑 | 具体的判断逻辑 |
| 是否生效中 | 必须打开。新建表单中默认是关闭的,关闭时保存的钩子不会执行 |
| 变量名称 | 变量类型 | 描述 |
|---|---|---|
objectType | Class<?> | 当前操作的对象类型 |
userContext | tech.muyan.api.security.MuyanAuthentication | 当前用户 |
application | grails.core.GrailsApplication | 当前的 grails 应用上下文 |
log | Closure<?> | 用于打印执行日志的 log 闭包 |
逻辑要返回一个 result 键,其值是包含 create 键的 Map:
// 允许用户创建该对象,create 必须放在 result 中
// Allow user to create this object, "create" must be wrapped in "result"
return [result: [create: true]] 2
返回值格式
必须把 create 包在 result 里。直接返回 [create: true] 时,权限查询接口会报错 Cannot invoke "java.util.Map.get(Object)" because "result" is null。
说明:
- 这个钩子只影响 权限查询接口
GET /permissions/<领域模型>/create的结果,也就是界面上是否显示新建按钮;通过POST /data/<领域模型>创建对象时,后端仍只按createRoleRequirement校验。 - 钩子返回
create: true,但用户不满足createRoleRequirement时,结果中的create仍为true,另外带上error: 1和一段提示信息。界面上仍会显示新建按钮,但提交时会被后端拒绝。 - 同一个领域模型配置了多个
CREATE钩子时,全部忽略,按createRoleRequirement判断。
# 动态修改和删除权限
Update/delete ability(UPDATE_DELETE)类型的对象钩子当前不会被执行:界面上仍然可以选这个类型,但选了不起作用(实测权限查询结果不受其影响,也没有产生执行记录)。需要按对象判断修改、删除权限时,请在 updateRoleRequirement / deleteRoleRequirement 上使用 自定义逻辑,并注意上文关于 object 的限制。
# 权限查询接口
前端通过以下接口决定显示哪些按钮,也可以在自己的页面或第三方系统中调用(路径需加 /api 前缀,认证方式见 平台 API):
# 能否创建某类对象
curl http://<服务器地址>/api/permissions/DynamicWebhook/create \
-H 'Authorization: Bearer <access_token>'
# {"create":true}
# 对一组对象的查看、修改、删除权限
curl -X POST http://<服务器地址>/api/permissions/DynamicWebhook/ \
-H 'Authorization: Bearer <access_token>' -H 'Content-Type: application/json' \
-d '{"ids":[1]}'
# {"1":{"view":true,"update":true,"delete":true}}
2
3
4
5
6
7
8
9
10