在很多初创团队开发的心理测试平台中,权限管理往往极其粗糙。
他们在数据库表里加个 role 字段,然后在代码里写上:if (user.role == 'admin') { 显示所有按钮 } else { 只显示答题按钮 }。
这种仅限于“控制能不能看见某个菜单或按钮”的权限,叫做功能级权限。
但在真实的省市级教育系统或医疗集团中,客户的组织架构极其复杂。以教育局为例:
- 市教研员:能看全市所有学校的数据报表。
- 某校校长:能看本校所有学生的数据,但不能看隔壁学校的。
- 高一(3)班班主任:只能看本班学生的数据,连高一(4)班的都不能看。
- 心理老师:能看全校处于“心理高危”状态的学生名单,但普通学生的隐私报告无权查看。
如果你只做功能级权限,那么这位高一(3)班的班主任点开“学生档案库”这个菜单时,他会看到全校 3000 名学生的所有测评报告!这是极其致命的隐私越权泄露。
为了解决这个问题,系统必须在极其底层的架构上,实现硬核的 数据行级权限(Row-Level Security, RLS)。
基础地基:基于角色的访问控制(RBAC)模型
正如橙星云架构团队在设计 B 端中台时所坚持的,RBAC 依然是权限系统的灵魂。
我们不应该直接给某个具体的张老师赋予“查看三班”的权限,而是:
- 创建一个角色:
班主任。 - 将这个角色赋予张老师。
- 在赋予时,带上极其关键的资源范围(Scope / Data Range)属性:
目标资源 = 高一(3)班。
代码层的拦截艺术:如何防止越权查询?
最大的挑战在于,如何保证即使程序员在写代码时忘记了加权限过滤条件,系统也不会发生数据泄露?
如果依靠程序员在写每个查询接口时,手动拼装 SQL:SELECT * FROM students WHERE class_id = 3
那么总有一天,某个新来的实习生会忘记加上 WHERE,直接写个 SELECT *,把全校数据给查出来。
极客方案:MyBatis/Hibernate 数据底层拦截器
我们必须在关系型数据库的查询底层,筑起一道极其强硬的自动过滤网。
以 Java 生态为例,我们可以利用 MyBatis 的 Interceptor 或 Hibernate 的 Filter 机制。
当任何一个查询学生的 SQL 被抛到数据库底层的瞬间,拦截器会无情地介入:
- 抓取上下文:拦截器从当前线程(ThreadLocal)中抓取到发起请求的张老师的信息。
- 解析数据边界:发现张老师的权限范围是“仅限高一(3)班的组织架构 ID = 1003”。
- 强制改写 SQL(AST 语法树注入):拦截器将原本毫无防备的
SELECT * FROM students,极其暴力地强行改写为:
SELECT * FROM students WHERE org_id IN (1003) AND (原本的其他过滤条件)
通过这种在极其底层的 SQL AST(抽象语法树)层面的强制劫持与改写,我们彻底消灭了对程序员人性的依赖。无论业务代码写得多么奔放,只要 SQL 流经这道关卡,它都会被死死地锁在当前用户的合法数据边界之内。
极致复杂的动态规则引擎
针对那位特殊的“心理老师”,他的权限规则不仅和组织架构有关,还和数据的状态有关(只能看处于危机状态的档案)。
面对这种极其变态的行级权限,普通的 org_id 拦截已经不够用了。
技术团队需要引入动态表达式规则引擎(如 SpEL 或 OGNL)。
在配置中心,我们将该角色的权限配置为一段极度灵活的表达式:#record.schoolid == #currentUser.schoolid && #record.crisis_level >= 3
当心理老师试图打开某份报告时,系统利用规则引擎,将这份报告的数据和该老师的信息注入引擎中进行毫秒级的运算。只有返回 true,拦截器才会放行,否则直接抛出冰冷的 HTTP 403 Forbidden。
总结
在 B 端 SaaS 中,如果不做数据行级权限,所有的功能权限都如同虚设。
通过将极其复杂的组织架构边界转化为动态的规则表达式,并在最底层的 ORM 框架中进行强硬的 SQL 拦截与改写,技术团队为脆弱的心理隐私数据套上了一层无懈可击的金钟罩。在这套极其严密的 RBAC 体系下,没有任何人能够跨越权限的雷池一步。
