如何在心理平台上实现极其严密的 RBAC 数据行级权限(Row-Level Security)

不要指望业务代码自己去拼WHERE条件。在ORM底层注入AST拦截器与动态SpEL引擎,硬核构筑不惧人为疏漏的数据行级越权防线。

在很多初创团队开发的心理测试平台中,权限管理往往极其粗糙。
他们在数据库表里加个 role 字段,然后在代码里写上:
if (user.role == 'admin') { 显示所有按钮 } else { 只显示答题按钮 }

这种仅限于“控制能不能看见某个菜单或按钮”的权限,叫做功能级权限
但在真实的省市级教育系统或医疗集团中,客户的组织架构极其复杂。以教育局为例:

  • 市教研员:能看全市所有学校的数据报表。
  • 某校校长:能看本校所有学生的数据,但不能看隔壁学校的。
  • 高一(3)班班主任:只能看本班学生的数据,连高一(4)班的都不能看。
  • 心理老师:能看全校处于“心理高危”状态的学生名单,但普通学生的隐私报告无权查看。

如果你只做功能级权限,那么这位高一(3)班的班主任点开“学生档案库”这个菜单时,他会看到全校 3000 名学生的所有测评报告!这是极其致命的隐私越权泄露。

为了解决这个问题,系统必须在极其底层的架构上,实现硬核的 数据行级权限(Row-Level Security, RLS)

基础地基:基于角色的访问控制(RBAC)模型

正如橙星云架构团队在设计 B 端中台时所坚持的,RBAC 依然是权限系统的灵魂。
我们不应该直接给某个具体的张老师赋予“查看三班”的权限,而是:

  1. 创建一个角色:班主任
  2. 将这个角色赋予张老师。
  3. 在赋予时,带上极其关键的资源范围(Scope / Data Range)属性:目标资源 = 高一(3)班

代码层的拦截艺术:如何防止越权查询?

最大的挑战在于,如何保证即使程序员在写代码时忘记了加权限过滤条件,系统也不会发生数据泄露?

如果依靠程序员在写每个查询接口时,手动拼装 SQL
SELECT * FROM students WHERE class_id = 3
那么总有一天,某个新来的实习生会忘记加上 WHERE,直接写个 SELECT *,把全校数据给查出来。

极客方案:MyBatis/Hibernate 数据底层拦截器

我们必须在关系型数据库的查询底层,筑起一道极其强硬的自动过滤网。
以 Java 生态为例,我们可以利用 MyBatis 的 Interceptor 或 Hibernate 的 Filter 机制。

当任何一个查询学生的 SQL 被抛到数据库底层的瞬间,拦截器会无情地介入:

  1. 抓取上下文:拦截器从当前线程(ThreadLocal)中抓取到发起请求的张老师的信息。
  2. 解析数据边界:发现张老师的权限范围是“仅限高一(3)班的组织架构 ID = 1003”。
  3. 强制改写 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 体系下,没有任何人能够跨越权限的雷池一步。

Leave a Reply

Your email address will not be published. Required fields are marked *