# 权限控制架构设计(Tenant-User-Role) ## 目标 - 保证租户隔离:任何用户只能访问其 Token 绑定的租户数据 - 保证授权一致:同一类请求在所有入口都遵循同样的鉴权与授权规则 - 保证可演进:RBAC 可以稳定运行,ABAC 可逐步引入策略引擎与审计闭环 ## 分层职责与落点 ### 认证(Authentication):中间件层 - 位置:`src/middleware/auth.rs` - 输入:`Authorization: Bearer ` - 输出:解析/验签 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 作为数据库兜底隔离层。