做过业务系统的人应该都有这种体会:权限模块在需求文档里通常写不了五行字,但真正落地的时候,却能折磨掉整个迭代周期。菜单要按角色区分,按钮要按权限控制,数据要按范围隔离,PC端刚理清楚,APP端又冒出来一堆系统级权限要处理。前几天我把 JNPF 的权限体系完整过了一遍,PC 和 APP 两端一起接入,不得不承认,它的授权逻辑做得确实直观——配置完角色回头一看,整个权限流转路径一目了然,连产品经理都能看懂。
这篇内容我打算顺着 JNPF 的权限设计思路,把模型到落地完整拆开讲。包括用户-角色-权限这套经典模型怎么落地、菜单按钮权限怎么配、行级数据权限怎么设、PC 端和 APP 端如何保持一套配置两端生效,最后再整理一些权限排查的实战经验。如果你正在做权限设计,或者打算在低代码平台上落地权限管控,这篇应该能帮你少踩几个坑。
1. JNPF 权限体系整体拆解:一套权限设计,两端同时接管
1.1 权限设计的第一步:理清用户、角色、组织三层模型
网上关于权限的资料一搜一大把,但真正动手做的时候,很多人第一步就歪了——上来就纠结某个按钮该放哪,却忽略了最基础的模型。
不管什么系统,权限的本质都是同一件事:判断"某个主体能不能对某个资源执行某个操作"。你肯定见过 Windows 弹窗提示"你需要来自 Administrators 的权限才能删除",也见过 Linux 下普通用户执行 sudo 命令时被告知"不在 sudoers 文件中",包括系统文件被 TrustedInstaller 锁定后、哪怕管理员也删不掉的经典场景。这些听起来像是系统问题,底层逻辑和业务系统里的权限校验是完全一致的:资源有归属、操作有范围、主体有等级,系统只是按预设的规则做了一次身份核对。
JNPF 把这种核对抽象成了三层模型:用户、角色、组织。
- 用户:登录系统的自然人,对应唯一的账号。
- 组织:天然的用户分组,通常按公司部门、分支机构来建,一个人只能属于一个主组织,但可以兼职多个其他组织。
- 角色:权限的载体,一头绑定用户,另一头绑定功能权限和数据权限。
很多刚接触权限设计的人会直接给用户分配权限,用户少的时候问题不大,一旦超过五十人、一百人,维护成本就开始失控。张三离职要逐个清理权限,李四调岗要重新配一堆入口,王五一个账号兼顾多种职责的时候,权限列表乱到根本没法审计。角色这层抽象的价值在于,把"人"和"权限"彻底解耦——人怎么变不影响权限规则,权限怎么调不影响账号结构。
我给一个简单的对照表,帮你快速理解这几层模型各自的角色:
| 实体 | 解决的问题 | 典型例子 |
|---|---|---|
| 用户 | 我是谁 | 工号 10086 的小张 |
| 组织 | 我在哪个部门 | 销售一部 / 华东大区 |
| 角色 | 我能做什么 | 销售员、销售经理、财务审核 |
| 功能权限 | 我能看到哪些入口 | 客户管理菜单、订单新增按钮 |
| 数据权限 | 我能看到哪些数据行 | 仅本人客户 / 本部门订单 |
这套模型不是 JNPF 发明的,但它把模型的落地做到了配置化,这是我认为最值得借鉴的地方。
1.2 授权逻辑的直观呈现:从角色到权限的流转路径
配置权限最怕什么?怕东一头西一头,配完了自己都不知道用户最终能看到什么。JNPF 的权限管理页做得比较清楚,角色是中心,所有授权都从角色入口走。
我以"销售经理"这个角色为例,带着你走一遍完整的配置流程:
- 在角色管理里新建角色"销售经理",可以填备注说明适用范围。
- 在角色成员 Tab 下,把销售一组的小张、小李添加进去。
- 切到功能权限 Tab,按树形菜单勾选"客户管理""订单管理""数据报表"等菜单;在按钮层面勾选"新增""编辑""导出""分配"。
- 切到数据权限 Tab,数据范围选择"本部门及以下部门",保存。
- 配置完成后,使用小张的账号重新登录,会看到菜单只渲染了勾选过的模块,订单列表页自动只展示他所在部门的数据。
这套流程走下来,你基本就理解了权限的流转路径:用户登录 → 携带用户ID → 权限框架加载角色集合 → 汇总功能权限编码和数据权限规则 → 前端动态渲染菜单和按钮 → 后端接口对每个操作做二次校验。
权限数据在底层是下面这些表的协作结果,理解数据来源之后排查问题会快很多:
| 表/关系 | 作用 |
|---|---|
| 用户表、组织表 | 账号身份与归属 |
| 角色表 | 角色定义 |
| 用户-角色关系 | 人与角色的多对多绑定 |
| 菜单表、按钮表 | 功能资源的元数据定义 |
| 角色-菜单按钮关系 | 角色拥有的功能权限 |
| 角色-数据规则关系 | 角色限定的数据范围规则 |
2. PC/APP 全场景覆盖:同一套授权配置,两套终端同步生效
2.1 PC端菜单与按钮权限的配置要点
先聊功能权限。功能权限分两层:菜单权限和按钮权限。菜单权限控制的是"这个页面你进不进得来",按钮权限控制的是"进到页面后,这个操作你能不能点"。
很多前端朋友应该都搜过"vue 按钮权限 怎么控制"这类问题。在 JNPF 里,按钮权限的配置其实很直接:权限管理页把菜单和按钮按树形结构列在同一棵树上,父级是菜单,子级是按钮。给角色勾选菜单时,子按钮默认不会一起勾上,需要单独点选。勾选哪个按钮,前端渲染的时候才会显示哪个按钮。
这里补充一个很多团队都踩过的设计问题:按钮权限到底该由前端隐藏,还是后端拦截?我的建议是,前端控制显隐只是为了用户体验,不构成安全防线。按钮隐藏得再彻底,接口地址不一样能直接调用。真正的防线在后端。JNPF 的按钮权限在配置时其实对应着一组权限编码,这些编码前端用来判断渲染,后端接口的接口拦截同样会校验同一组编码。两端校验同一个东西,权限才能真正闭环。
我建议在初始化项目的时候就把按钮编码的命名规范定好,比如统一用模块+动作的格式:customer:add、customer:delete、order:export。没有规范的话,配置端和后端校验就很容易对不上,权限配了却始终不生效。JNPF 的菜单和按钮管理里可以维护编码,编码一旦固定,就不要频繁改。
2.2 移动端权限的适配细节与特殊处理
APP 端的权限和 PC 端有一处明显差异:除了业务权限,还有一大块设备系统权限。定位权限、相机权限、相册权限、通知权限、麦克风权限,这些在安卓和 iOS 上的申请机制各不相同,有些版本对运行时权限的约束还在收紧,比如安卓近几个版本对后台定位和录屏权限的限制就改过好几轮。如果你所在的项目正好踩了"用户可以进入扫码页面,但相机权限一直没弹窗"的坑,不用怀疑,第一步先去看宿主 APP 的系统权限申请流程。
JNPF 移动端的处理方式是双线并行:业务权限继续复用服务端那一套配置,设备系统权限则由移动端框架统一管理。页面在需要定位的时候,调用统一 API 发起定位权限申请,用户在系统弹窗里选择允许或拒绝;如果之前选择了"不再询问",页面会引导用户到系统设置里手动打开。
这两个权限体系,一个管数据入口,一个管设备能力,最终在页面上共同决定功能是否可用。比如扫码打卡页面:业务权限不够的话,菜单都不渲染,入口直接消失;业务权限够了但相机权限没开,页面能进,但点扫码后提示去设置。这样拆开处理,权限职责更清晰,排查问题也更省力。
2.3 一套配置多端同步的实现原理
JNPF 的 PC 和 APP 共用同一个权限配置,不用两端重复设置,这个特性是它比较吸引人的地方。
实现的原理并不玄乎:权限数据统一存在服务端,PC 端和 APP 端只是消费同一份数据。用户登录后,客户端调用同一个权限接口,拿到的菜单树和权限标识是一样的。区别只在前端渲染框架不同,一个渲染成 PC 端桌面布局,一个渲染成移动端列表布局。
这里有个细节要注意:JNPF 的菜单支持平台标识。同一个菜单可以同时勾选 PC、APP、小程序,也可以指定只在一个端生效。比如某些报表后台操作只适合在 PC 端做,把菜单的平台标识只勾选 PC,APP 端就不会展示。配置的时候如果发现 APP 端少了某个菜单,第一反应应该是去菜单管理里查平台标识,而不是反复重登账号、清缓存。
移动端打开菜单背后的逻辑也值得一提:APP 端登录后,会拉取当前用户菜单树并生成本地路由,打开一级页面时按需加载。这套机制和 PC 端保持一致,所以无论你在哪个端改权限,另一个端重新拉取权限数据后就会同步生效。
3. 行级数据权限的设计与实现:从入门到落地
3.1 行级权限是什么:从"能不能看到这个功能"到"能看到哪些数据"
功能权限和数据权限是两个维度。功能权限回答的问题是"你能不能用这个功能",数据权限回答的问题是"你用这个功能时,能看到哪些数据"。很多人理解权限的时候只关注了前者,忽略了后者,结果就出现了"销售员能进客户列表页,但能看到全公司所有客户的电话"这种数据安全事件。
行级权限也叫行级数据权限,在 Java 后端领域是经典的实现难题——写 SQL 过滤条件、拼接权限范围、做缓存优化,每一环都要设计。这也是"行级权限 java"这类搜索词长期有热度的原因。
JNPF 把行级权限做成了可视化配置,不需要写 SQL 就能实现常见的数据范围控制。在角色的数据权限配置里,通常有以下几种数据范围选项:
| 数据范围 | 含义 | 适用场景 |
|---|---|---|
| 仅本人 | 只能看自己创建或负责的数据 | 销售员的客户、普通员工的报销单 |
| 本部门 | 看本部门所有人的数据 | 部门主管的管理视图 |
| 本部门及以下部门 | 看自己部门加所有下级部门的数据 | 区域经理、大区负责人 |
| 全部数据 | 不限制范围 | 管理员、财务、老板 |
| 自定义规则 | 按条件表达式筛选 | 财务只看已审批单据等复杂场景 |
我实际配置的时候,最常用的是前四种,自定义规则更多用在"按状态过滤""按金额区间过滤"这类特殊需求上。以"仅本人"为例,平台后端会解析成类似WHERE create_by = #{当前用户ID}的条件,追加到列表查询 SQL 上。整个过程配置完即可生效,不需要写 Java 代码,这就是低代码平台在这一块的核心优势。
3.2 数据权限规则表达式实操:复合条件怎么配
简单场景用预设范围就行,但业务总会冒出一些组合条件,比如"既能看本人创建的数据,也能看所有已归档的数据"。这种需求单靠一个范围选项搞不定,需要用到自定义数据权限规则。
JNPF 的自定义规则通常由三部分组成:字段、操作符、值来源。值来源支持当前用户ID、当前用户部门ID、当前用户角色、固定值、自定义 SQL 片段。比如讨一个实际场景:
- 场景:销售员能看到自己名下的客户,以及所有状态为"已归档"的客户。
- 配置思路:把规则配置为两条,用 OR 连接:
- 规则一:
customer.owner_id等于当前用户ID。 - 规则二:
customer.status等于固定值"已归档"。
- 规则一:
平台在执行查询时,会把第一条规则解析成owner_id = '10086',第二条解析成status = 'archived',两条规则做 OR 拼接后成为最终查询条件的一部分。
这里有一个我在项目里真实踩过的坑:自定义规则的条件优先级。规则多了以后,OR 和 AND 的组合顺序会直接影响结果范围。默认情况下,多规则之间是 OR 关系,也就是放宽了限制。如果你的业务要求多个条件同时满足,就得在规则配置里显式指定 AND 连接。最稳妥的做法是,配完规则后,先用测试账号跑一遍列表,抽查几条边界数据,确认没有越权也没有漏数据。尤其注意时间范围条件,日期字段如果存的是带时分秒的时间戳,而规则里用的格式不对,很容易查不出数据。
还有一个建议:自定义规则尽量保持在三条以内。规则越长越难验证,后期别人接手也看不懂。我见过一个客户把数据权限规则配了七八条,员工离职之后根本没人敢动那个角色。权限配置也是需要管理的资产,简洁本身就是一种可维护性。
3.3 数据权限与功能权限的组合逻辑:前端、接口、数据三层校验
权限治理到位的系统,通常会做三层校验,顺序是:前端控制显隐、后端接口校验、数据层行级过滤。
拿"销售员删除客户"这个操作举例:
- 第一层,前端按钮权限。角色没勾选"删除"按钮,页面不渲染删除按钮,普通用户从界面上根本没有删除入口。
- 第二层,后端接口校验。有人绕过前端直接调用删除接口,服务端会校验当前用户是否拥有
customer:delete这个权限编码,没有就返回 403。 - 第三层,数据权限校验。假设用户拥有删除权限,但只能删自己名下的客户,后端处理删除的时候还会做一次行级校验,确保被删除的数据确实在用户的数据范围内。
JNPF 平台内置的 CRUD 接口会自动执行这三层校验,这也是为什么列表页、表单页这类标准功能基本不用写额外权限代码。但如果你在业务里自定义了 API,那么后两层校验就得自己在接口里接上平台的权限框架,或者调用平台提供的权限校验方法。这一段很多小伙伴会忽略,测试阶段发现接口洞的时候才恍然大悟:原来按钮隐藏不等于安全。
4. 授权逻辑背后的常见问题与排查手册
4.1 刚改了权限不生效,问题可能出在哪几个环节
这个问题几乎每次权限培训都会被问到。改了角色权限,重新登录,界面还是老样子,最常见的几个原因如下,按优先级从高到低排查:
- 缓存。浏览器缓存、APP 本地缓存、服务端 Redis 缓存,只要有一层缓存没刷新,新权限就不会立刻生效。JNPF 登录时会重新拉取权限快照,所以改完权限后让对应用户退出重登是第一步。
- 多角色叠加。用户可能有多个角色,A 角色没有某个菜单,但 B 角色有,最终权限取的是并集。用户看到菜单不代表 A 角色配置成功,可能只是 B 角色的权限在起作用。要确认配置是否生效,最好用一个只挂单一角色的测试账号去验证。
- 菜单平台标识。PC 端能看到,APP 端看不到,优先检查菜单的平台标识是否勾选了 APP。这项最常见,也最容易忽略。
- 权限编码不匹配。按钮权限编码和接口校验编码不一致,就会出现前端该显示的显示了、后端却拒绝访问,或者前端隐藏了、后端却能直连的诡异现象。
借用前面提过的 Windows 权限问题来类比:你"需要来自 Administrators 的权限才能删除",但切换了管理员账号还是删不掉的时候,是不是会先怀疑文件被 TrustedInstaller 锁定、而不是反复换账号重试?业务系统的权限排查也是同一个思路,先看清当前账号实际属于哪个角色、目标资源的权限由谁的规则控制,再去验证修改为什么没有生效。盲目重登十次,不如花三十秒把权限链路过一遍。
4.2 按钮权限与接口校验的联动问题
前端隐藏了按钮,调接口却依然能拿到数据,这个问题被问过很多次。
先说结论:按钮权限的前端隐藏只负责用户体验,接口校验负责真正的安全。两者如果不能对齐,权限体系就是破的。
JNPF 在平台内置功能上对齐做得比较好,因为权限标识在配置端和后端框架里是同一套。问题大多出在扩展开发上。你在代码生成器里加了一个自定义按钮,也在页面上配置了按钮编码,但如果忘了在后端接口里加上同样的权限校验,那这个按钮就只是"看上去有权限控制"。
很多团队处理这个问题的方式是:每次发布权限相关需求,QA 必须做一次接口直调测试。也就是绕过前端页面,用工具直接调用带权限的 API,验证无权限用户是否会被拒绝。这个方法成本低、效果明显,强烈建议纳入权限测试用例。
4.3 权限不够用的时候,如何优雅扩展
平台默认的权限模型覆盖了大多数业务场景,但总会有一些个性化需求,比如:
- 外部系统用户同步:HR 系统里维护组织架构,需要定时同步到 JNPF。
- 动态授权:某些用户登录后要根据外部业务状态临时追加角色。
- 自定义数据源:数据权限的自定义规则需要支持跨库查询。
面对这些场景,优先考虑在平台外层做集成,而不是直接改平台核心权限模块的源码。JNPF 提供了用户、角色、组织相关的接口,可以在外部系统里做同步脚本;登录后也支持通过事件或扩展逻辑对临时权限做调整。改源码一时爽,后期版本升级的时候会非常痛苦,这是我长期做平台类项目攒下的教训。
还有一点经验之谈:权限配置最好在项目初期就定好规则。见过不少项目上线后才发现权限模型撑不住,这时候再调整角色设计,迁移成本远比想象的高。先把用户类型梳理清楚,把职责权限画成矩阵,再往平台上落,整个过程会顺畅很多。
我个人在权限配置上踩得比较深的一个坑,是把它当成纯前端的事来做了。后来被测试在接口层找到漏洞,才真正养成"前端控制显隐、后端控制接口、数据层再兜底"三层校验的习惯。JNPF 把这套逻辑做成可视化之后,最大的价值不是省了写代码的时间,而是让整个团队的沟通成本降下来了——产品经理能看懂权限规则,测试能说清楚权限预期,后端不用再靠口述解释数据范围。如果你们团队也正在权衡权限方案,不妨先按角色把功能权限和数据权限分开梳理,再对照本文的配置路径过一遍,很多看似复杂的东西,其实只是缺少一个直观的载体。