系统设计 / 访问控制 · 从 RBAC 查表到 policy 判定函数 / Server 层:从 Middleware 到 Repository 的逐层拦截 待审核 7 / 23
Server · 分层拦截

Server 层:从 Middleware 到 Repository 的逐层拦截

安全校验的最终位置在后端(UI 层的显隐只是体验)。一个请求穿过一条流水线,每一层管一件事:Middleware 认身份和角色 → Router 声明这个 API 要什么 capability → Controller 只编排、不判权 → Service 带上业务上下文 → Domain 执行业务规则 → Repositoryscope 过滤数据。

1 · 请求在六层流水线上的行程

请求可能在三处被挡下:Router 判「这个角色一般能不能做」,Domain 判「这个业务状态允不允许」,Repository 判「这一条记录在不在我的 scope 里」。

图 1-1 · 一次请求逐层穿过 Server 流水线的过程。可选角色、操作与目标对象,单步或一次执行到底,观察请求在哪一层放行、在哪一层被拦截。

2 · can / assert / scope 三个原语

表 2-1 · 三个授权原语各自回答的问题与使用位置。
API 问的问题 用在哪
can(key) 有没有这条能力?返回 boolean UI 显隐、分支判断
assert(key) 没有就抛 403,有就继续 Router / Service 守卫,拦截无权限的请求
scope(key) 这条能力的数据范围是什么(Full/Group/Self)? Repository 拼 WHERE 条件

注 · ScopeResolver 把 scope(key) 得到的范围翻译成具体的查询约束——输入 (capability, 当前用户),输出 { where, 或 拒绝 }。它是「对谁做」在数据层的落地点:Self → WHERE owner_id = meGroup → WHERE group_id = me.groupFull → 无约束

警示 · 对象级授权(object-level)必须下沉到数据层。Router 的 assert(order:cancel) 只回答「这个用户一般能不能取消订单」;但「能不能取消这一张订单——它是不是在我的 scope 里」要等真的取到这条记录才知道。所以 scope 过滤放在 Repository:要么进 WHERE(查不到 = 不存在),要么取出后断言 owner === me。只在 Router 拦截,等于只做了类级授权,会遗漏「越权访问他人某一条记录」的情况。

建议 · 分层各司其职,职责不互相渗透:Controller 只做编排、不写权限判断;业务规则(「done 的单不能取消」)属于 Domain,不要放进 Controller 或 SQL;Repository 只负责 scope 过滤,不涉及业务。职责划分清楚,权限逻辑才便于排查、便于测试。

3 · 换一个 id 的越权

Router 上那句 assert(commerce:order:read) 与 URL 里的 id 无关,同一个角色的任何请求它都放行。于是把 /api/orders/1001 改成 1002 会发生什么,完全由数据层那道 scope 过滤决定:过滤在,查不到就是 404;过滤不在,别人的订单原样返回。这类缺陷在 OWASP API Security Top 10 里排第一位,称作 BOLA(broken object level authorization),旧称 IDOR。

前端的隐藏与置灰对它没有任何作用,因为请求可以手写。同样漏在这一层的还有两种写法:批量端点 GET /api/orders?ids=1001,1002,1003 逐个 id 取数据却只在入口 assert 一次;PATCH 接口把请求体整个灌进实体,让攻击者顺手改掉 owner_id

图 3-1 · 同一条请求在数据层执法与不执法两种配置下的响应。可切换目标 id、当前 scope 与数据层开关,观察越权何时成立,以及拦下时 404 与 403 的差别。

警示 · 拦下之后返回哪个状态码是一次信息泄漏的权衡。403 承认这个 id 存在,攻击者据此可以逐个扫描出有效 id 的范围;404 把「无权」与「不存在」合并成同一个回答,扫描便拿不到信号。代价是排障时两种情况分不开,所以真实原因要落在服务端日志里,而不是响应体里。

4 · 参考文献

  1. OWASP. (2023). API1:2023 Broken Object Level Authorization. OWASP API Security Top 10. 对象级授权缺失为何长期居首,以及批量端点与对象 id 枚举的攻击面。owasp.org