Server 层:从 Middleware 到 Repository 的逐层拦截
安全校验的最终位置在后端(UI 层的显隐只是体验)。一个请求穿过一条流水线,每一层管一件事:
Middleware 认身份和角色 → Router 声明这个 API 要什么 capability →
Controller 只编排、不判权 → Service 带上业务上下文 →
Domain 执行业务规则 → Repository 按 scope 过滤数据。
选一个场景,逐层单步执行,看请求在哪一层放行、在哪一层被拦截。
选好场景,点「下一步」。
can / assert / scope 三个原语
| API | 问的问题 | 用在哪 |
|---|---|---|
can(key) | 有没有这条能力?返回 boolean | UI 显隐、分支判断 |
assert(key) | 没有就抛 403,有就继续 | Router / Service 守卫,拦截无权限的请求 |
scope(key) | 这条能力的数据范围是什么 (Full/Group/Self)? | Repository 拼 WHERE 条件 |
scope(key) 得到的范围,翻译成具体的查询约束
—— 输入 (capability, 当前用户),输出 { where, 或 拒绝 }。
它是「对谁做」在数据层的落地点:Self → WHERE owner_id = me、
Group → WHERE group_id = me.group、Full → 无约束。
assert(order:cancel)
只回答「这个用户一般能不能取消订单」;但「能不能取消这一张订单(它是不是在我的 scope 里)」
要等真的取到这条记录才知道。所以 scope 过滤放在 Repository——
要么进 WHERE(查不到=不存在),要么取出后断言 owner === me。
只在 Router 拦截,等于只做了「类级」授权,会遗漏「越权访问他人某一条记录」的情况。
Controller 只做编排、不写权限判断;业务规则(「done 的单不能取消」)属于
Domain,不要放进 Controller 或 SQL;Repository 只负责 scope 过滤,不涉及业务。
职责划分清楚,权限逻辑才便于排查、便于测试。