88 lines
3.8 KiB
Markdown
88 lines
3.8 KiB
Markdown
# 权限控制架构设计(Tenant-User-Role)
|
||
|
||
## 目标
|
||
|
||
- 保证租户隔离:任何用户只能访问其 Token 绑定的租户数据
|
||
- 保证授权一致:同一类请求在所有入口都遵循同样的鉴权与授权规则
|
||
- 保证可演进:RBAC 可以稳定运行,ABAC 可逐步引入策略引擎与审计闭环
|
||
|
||
## 分层职责与落点
|
||
|
||
### 认证(Authentication):中间件层
|
||
|
||
- 位置:`src/middleware/auth.rs`
|
||
- 输入:`Authorization: Bearer <access_token>`
|
||
- 输出:解析/验签 Token 并注入 `AuthContext { tenant_id, user_id, roles, permissions }`
|
||
- 选择理由:
|
||
- 认证属于横切关注点,放在中间件可避免每个 Handler 重复实现
|
||
- 401/403 与错误码可统一,便于网关与客户端处理
|
||
- 便于埋点:在统一入口记录审计字段(IP/UA/结果/耗时)
|
||
|
||
### 租户上下文(Tenant Context):中间件层
|
||
|
||
- 位置:`src/middleware/mod.rs`
|
||
- 行为:
|
||
- 优先使用 `AuthContext.tenant_id` 注入 `TenantId`
|
||
- 兼容 `X-Tenant-ID`,若 Header 与 Token 同时存在则强制一致,否则返回 403
|
||
- 选择理由:
|
||
- 租户隔离是安全边界,必须在最靠前的入口稳定执行,避免业务分支遗漏
|
||
|
||
### 授权(Authorization):Service 层为规则源,Handler/中间件为拦截点
|
||
|
||
当前实现采用“规则在 Service、拦截在 Handler”的折中:
|
||
|
||
- 规则源(Service):`src/services/authorization.rs`
|
||
- 负责 RBAC 查库:用户→角色→权限
|
||
- 产出统一决策(允许/拒绝)并返回 `AppError::PermissionDenied`
|
||
- 拦截点(Handler):在具体接口中调用 `AuthorizationService::require_permission(...)`
|
||
- 选择理由:
|
||
- Handler 更接近“路由-动作”语义,决定某个接口需要什么权限更直观
|
||
- Service 只负责“如何判断”,不关心“哪个路由需要什么”
|
||
|
||
当路由数量增长后,建议演进为“拦截点上移到中间件/Layer”:
|
||
|
||
- 给不同 Router 分组挂载不同的授权 Layer(如 tenant 管理、用户管理、角色管理)
|
||
- 或引入宏/属性(例如 `#[require("tenant:write")]`)生成统一的 Guard
|
||
|
||
### ABAC:建议落点(后续实现)
|
||
|
||
ABAC 需要资源属性、环境属性、请求属性,通常依赖:
|
||
|
||
- 资源属性:DB 查询或缓存读取(属于业务领域)
|
||
- 环境属性:IP、时间、设备风险、地理位置(属于安全/风控)
|
||
- 请求属性:请求参数与上下文字段
|
||
|
||
因此 ABAC 的“策略评估”建议由独立模块/服务承担(例如 `PolicyEngine`),并由 Handler 在调用业务 Service 之前完成决策;决策审计由统一审计组件记录。
|
||
|
||
## 审计与可观测性
|
||
|
||
- 建议在认证/授权路径内记录审计事件:
|
||
- actor:tenant_id、user_id
|
||
- decision:allow/deny
|
||
- policy:匹配的 RBAC 权限或 ABAC 策略 ID
|
||
- cost:授权耗时
|
||
- 当前库 `common-telemetry` 已提供统一错误返回;建议后续在错误响应中补齐 trace_id(从 tracing context 获取)。
|
||
|
||
## 数据隔离方案对比(建议)
|
||
|
||
### 现状:共享库共享表(tenant_id 过滤)
|
||
|
||
- 优点:实现简单、性能可控、易于水平扩容
|
||
- 风险:任何遗漏 `tenant_id` 条件都可能造成越权
|
||
- 现有防线:
|
||
- 中间件注入 `TenantId`
|
||
- Service 查询必须显式绑定 tenant_id
|
||
|
||
### 演进方案 A:独立 Schema(每租户一个 schema)
|
||
|
||
- 优点:逻辑隔离更强,可按租户迁移/清理
|
||
- 缺点:管理成本高,连接池与迁移复杂
|
||
|
||
### 演进方案 B:Postgres Row-Level Security(RLS)
|
||
|
||
- 优点:隔离在数据库层兜底,减少“遗漏 where tenant_id”的风险
|
||
- 缺点:策略配置复杂,需要设置 session 变量(如 `app.tenant_id`)并在连接池层保证一致
|
||
|
||
建议:当租户数量增长或合规要求上升时,引入 RLS 作为数据库兜底隔离层。
|
||
|