生态产品多个设计器现已发布候选版本

查看生态总览
跳转到内容

权限与脱敏(前端) ​

前端在页面 / 字段 / 操作三个层级按权限码过滤,并在服务端脱敏的基础上做只读与标识提示。本页讲前端这一侧怎么写;判定规则本身(权限码、数据范围、ABAC、约束规则)见 权限模型。

前提:前端过滤是体验,不是安全边界 ​

服务端才是安全边界

前端隐藏按钮只是不让用户看到无效入口。真正的门控在服务端:[PermissionAuthorize] 特性 + 授权快照判定。

绕过前端直接调接口一定会被后端拒绝——所以不要因为前端已经隐藏了按钮就省掉后端的权限标注。

权限码 ​

以 : 分段,多数是 module:resource:action(如 saas:user:read、saas:tenant-edition-permission:grant),也有两段的 module:action(如 workflow:execute)。超管在服务端用字面通配 *(不是段级 *:*:*)。

前端不把 * 当通配,只按下发的权限码与按钮码精确匹配:服务端下发时已把超管的 * 展开成当前上下文生效的全部码,作用侧不含当前上下文的码(如平台态里的部门、数据范围等租户侧权限)不下发、鉴权时 * 也不放行。前端再拿 * 短路,就会在平台态露出服务端必拒的操作。

真源在后端 SaasPermissionDefinitions(Saas 模块)与各模块自己的权限码类。前端只是消费者——写权限码字符串时去后端对一下,别凭记忆写。

用户的权限码集合来自登录后的 GET /api/Auth/Permissions。

权限码不在 JWT 里

Access Token 里没有具体权限码(超管例外,只放一个 * 作快路径)。这带来一个好性质:管理员改了授权,用户不用重新登录就生效——前端下次拉取权限或后端下次判定时就是新的。

三级过滤 ​

一、页面级 ​

由路由守卫按 meta.roles / meta.permissions 校验,无权跳 403。数据来自后端 PageDescriptor.PermissionCode。

PermissionCode 为空的页面是纯展示菜单,不做门控。

二、字段级 ​

Schema 页里字段声明 permission,无权时该列与该搜索项整个不渲染(selectors.ts 的 isFieldPermitted):

ts
{ key: 'phone', title: t('identity.org.col_phone'), dataType: 'string',
  permission: 'saas:department:read' }

注意这是「不渲染」而不是「渲染成空」——用户看不到这一列存在。

三、操作级 ​

  • Schema 页的 actions[].permission:无权时按钮不出现。
  • PageSchema 上的 exportPermission / importPermission / removePermission(批量删除)/ statusPermission(批量启停):门控对应的内置按钮。其中只有 exportPermission 是严格门控(未声明就不显示导出),另外三个未声明时不做限制。

exportPermission 未声明 = 不显示导出

这是刻意的默认值选择:宁可漏显示,不可错显示。要导出按钮就必须声明权限码。

页面里手写门控 ​

没有包裹式权限组件——页面按钮/区块级门控统一是「usePermission() 派生 computed + 模板 v-if」这一种写法:

ts
const { hasPermission } = usePermission()
const canGrant = computed(() => hasPermission('saas:tenant-edition-permission:grant'))
vue
<XhButton v-if="canGrant">分配权限</XhButton>

usePermission()(~/hooks/usePermission.ts)提供:

方法说明
hasPermission(code | code[])是否有权限码(传数组时任一命中即通过)
hasRole(role | role[])是否有角色(传数组时任一命中即通过)
hasAnyPermission(codes[])任一命中即通过

统一用这一种写法

不要另造包裹组件或指令。全仓统一「computed + v-if」的好处是:审计权限点时一个 grep 就能找全,也不会出现「指令生效时机与渲染时机不一致」的坑。

字段级脱敏(FLS) ​

脱敏在服务端完成 ​

后端返回的敏感字段已经是打码后的值(如 138****5678),前端不再二次打码,页面也不需要为字段安全写任何代码:

  • 读:列表、详情、导出拿到的就是打码后的值;
  • 写:表单把拿到的脱敏值或空值原样交回,后端视为没改并还原原值——不会用 138****5678 覆盖真实号码;改动了只读字段,后端返回「字段「…」当前用户无修改权限」。

规则在「字段安全」页按实体、字段下拉配置,模型见 数据权限 · 字段级安全。

字段安全也门控查询条件 ​

服务端会剔除读受保护字段上的排序、过滤与关键字搜索字段,剔完没有有效排序时回退默认排序。

所以「排序点了没反应」「按某字段搜索没效果」优先怀疑字段安全,而不是前端 bug。

多租户 ​

数据按 TenantId 隔离,全局数据约定 TenantId=0。前端不需要手动处理租户过滤——它由后端在请求管道里完成,前端只按拿到的权限码集合渲染。

前端要处理的只有两件事:

事情说明
切换上下文走 authStore.switchContext(tenantId)(null 回平台,仅平台账号):服务端在目标上下文续接会话、签发新令牌、旧令牌失效;前端清掉用户信息与标签页后整页重建,不产生新登录/新设备
上下文判断UserInfo.isPlatform(当前是否在平台)、tenantName(当前租户名)、canAccessPlatform(是否平台账号);页头的上下文标识据此显示并进入控制中心

整页加载时路由守卫把用户信息与权限、菜单一起重取——租户不同,可用的功能也不同(受租户版本门控影响),本地缓存的用户信息不作为上下文依据。

排查 ​

现象原因
按钮不显示权限码写错(去后端 SaasPermissionDefinitions 对);或用户确实没这个权限
导出按钮不显示PageSchema.exportPermission 没声明
列不显示字段 permission 无权;或 visible: false
保存时报「无修改权限」字段安全规则让该字段对当前用户只读,而表单改了它的值
前端能点、后端 403正常——前端过滤是体验层,以后端为准。检查该接口的 [PermissionAuthorize] 是否与前端用的码一致
改了授权前端还是旧的重新拉 /api/Auth/Permissions;后端侧检查是否调了 InvalidateAuthorizationAsync

相关页面 ​

Released under The MIT License