Authorization 回答的不是“用户是否已经登录”,而是:这个可信调用者,此刻能否在这个租户内,对这个资源实例执行这个动作。答案由多种策略共同给出,并且必须能被审计、失效和复现。
1. 模块到底由哪两部分组成
授权能力跨越 Framework 与 Platform 两层,不能只阅读 src/Platform/Authorization:
| 所有者 | 主要类型 | 责任 |
|---|---|---|
| Framework Application | IAuthorizedRequest、AuthorizationDecisionService、各 Policy Evaluator、DataScopeResolver | 定义运行时决策协议和组合规则 |
| Authorization Contracts | RBAC、字段安全、共享、分权、权限申请合同与权限目录 | 提供模块公开契约和 owner 扩展点 |
| Authorization Application | 策略管理、有效权限解释、临时授权与 owner 目录校验 | 修改策略状态并触发事件与缓存失效 |
| Authorization Infrastructure | owner-local 持久化记录、Store、缓存与双 ORM Adapter | 保存关系、规则、分类、Feature 覆盖和临时授权 |
| Host 组合根 | CoreRuntime 与 Persistence 注册 | 先装配占位评估器,再用生产评估器和 Store 闭合依赖 |
Platform 模块管理“策略事实”,Framework 执行“策略计算”。业务模块不应查询授权表,也不应复制一套 if (roles.Contains(...))。
2. 一次请求如何变成授权决策
AuthorizationPipelineBehavior 位于事务行为之前。拒绝会直接返回 Authorization.Denied,Handler 不执行,事务也不应开始。这个顺序由 AuthorizationPipelineBehaviorTests.Deny_Should_Prevent_Transaction_From_Starting 固定。
3. 五类策略不是五道独立闸门
| 策略 | 何时 Allow | 何时 Deny | 不适用时 |
|---|---|---|---|
| RBAC | 非 Application 调用者拥有 {module}.{resource}.{action} | 不直接 Deny | Neutral |
| AppScope | Application 调用者拥有同名 Scope | 不直接 Deny | Neutral |
| ABAC | 第一条命中规则的裁决为 Allow | 第一条命中规则为 Deny;Store 异常也 Deny | 无规则或无条件命中为 Neutral |
| ReBAC Lite | 用户与 Case、Client 或 Billing 实例存在允许当前动作的关系 | 关系 Store 返回失败 | 无主体、无实例、无关系或非目标资源为 Neutral |
| Feature | tickets、chat、workflow、reporting 映射的 Feature 已启用 | 映射 Feature 被禁用或 Provider 返回 false | 未映射模块为 Neutral |
它们全部执行后再合并:任一 Deny 即拒绝;否则至少一个 Allow 才允许;全 Neutral 返回 NoMatchingPolicy。注册顺序影响第一条拒绝理由和 Allow obligations 的收集顺序,但 Feature 放在最后并不是 Deny 获胜的原因。
完整算法、缓存键和 DataScope 见 授权决策引擎。
4. 管理面与决策面必须分开理解
4.1 管理面
管理员通过 Command/Query 维护角色、用户角色、角色权限、ABAC、Feature、字段策略、数据分类、共享规则和分权授权。权限申请则由标准 Workflow 审批后产生有绝对期限的独立授权来源。写用例使用 owner-local Store,成功后发布集成事件并失效相应缓存。
4.2 决策面
业务请求携带 ResourceDescriptor 和 AuthorizationAction。评估器只读取可信主体、资源事实、规则 Store 与缓存;它不调用管理 Handler,也不把角色表直接暴露给业务模块。
public sealed record ApproveInvoiceCommand( string InvoiceId, decimal Amount, string Status, string Sensitivity) : ICommand<Result>, IAuthorizedRequest{ // ResourceDescriptor 同时服务 RBAC 权限码、ABAC 条件和实例级 ReBAC。 public ResourceDescriptor Resource => new( Module: "billing", ResourceType: "invoice", ResourceId: InvoiceId, TenantId: TrustedTenantId, Status: Status, Amount: Amount, Sensitivity: Sensitivity);
// 动作参与权限码派生,也会进入 ABAC Action 过滤和 ReBAC 动作白名单。 public AuthorizationAction Action => AuthorizationAction.Approve;
// 该值必须来自可信租户上下文,不能接受客户端请求体中的 TenantId。 private string TrustedTenantId => CurrentTenantAccessor.EffectiveTenantId;}上例是基于当前接口的完整声明示例,CurrentTenantAccessor 代表调用方自己的可信租户访问器。实际项目通常在构造 Command 前装入资源事实,或显式实现一个授权适配服务;不要让请求体自行声明所有者、租户或敏感级别。
5. 数据模型不是一个 Role 聚合
当前实现使用显式 Store Record 与 owner-local 关系记录,并没有文档早期所描述的 Role.Grant() 聚合:
| 模型 | 稳定键与边界 |
|---|---|
RoleRecord / RoleCatalogRecord | 数据库 Id 用于管理;角色名是关系行的租户内稳定业务键 |
UserRoleRecord / UserRoleRelationRecord | 用户稳定键来自 Identity owner;角色 Id 或名称进入 Store 后归一化为角色名 |
RolePermissionRecord / RoleModulePermissionRelationRecord | 角色名、Menu 模块码、权限码组成关系 |
PermissionRecord / PermissionCatalogRecord | 全局权限目录;由生成或确定性种子维护 |
AbacRuleRecord / AbacRulePolicyRecord | 租户内规则;条件和裁决按整数枚举持久化 |
FeatureDefinitionRecord + FeatureOverrideRecord | 全局默认值与租户覆盖分开保存 |
ResourceRelationRecord | 租户内用户—资源关系;不建立跨模块 CLR 导航 |
角色生命周期、稳定键、权限授予和当前兼容性门禁见 RBAC 管理与角色生命周期。
6. HTTP 管理入口
| 能力 | 代表路由 | 资源 / 动作 |
|---|---|---|
| 角色 | GET/POST /api/authorization/roles、PUT/DELETE /roles/{roleId} | authorization/role + View/Create/Update/Delete |
| 用户角色 | GET /users/{userId}/roles、POST/DELETE /users/{userId}/roles/{roleId} | authorization/user-role + View/Assign/Revoke |
| 角色权限 | GET/POST /roles/{roleId}/permissions、DELETE /roles/{roleId}/permissions/{permissionId} | authorization/role-permission + View/Grant/Revoke |
| 权限树 | GET /api/authorization/permissions/tree | authorization/permission + View |
| ABAC | GET/POST /api/authorization/abac-rules、PUT/DELETE /abac-rules/{ruleId} | authorization/abac-rule + View/Create/Update/Delete |
| Feature | GET /api/authorization/features、PUT /features/{featureCode} | authorization/feature + View/Update |
| 高级治理 | /field-security、/sharing、/delegated-admin、/permission-audit、/permission-simulation、/permission-requests | 各能力独立 View/Manage/Approve/Reject/Revoke 权限 |
当前 Grant/Revoke 命令已分别使用 AuthorizationAction.Grant 与 AuthorizationAction.Revoke,与公开权限目录一致。用户角色分配和撤销也按命令中的目标主体失效权限与菜单缓存,不再失效操作者。
7. 缓存与事件的真实边界
- 角色创建、更新、删除与角色权限变化发布事件,并按租户失效 Permission 决策缓存。
- ABAC 创建、更新、删除按有效租户失效 Permission 决策缓存。
- Feature 更新区分全局默认与租户覆盖,发布
FeatureChangedIntegrationEvent;全局默认变更失效全部 Feature 缓存,租户覆盖只失效目标租户。 - 决策缓存键覆盖调用者、角色、权限、租户、客户端和全部资源事实;Delegated 调用者绕过固定 TTL 决策缓存。
- DataScope 和 ReBAC 有独立缓存端口,它们不能依赖 Permission 缓存失效“顺便清掉”。
角色变更通过 AuthorizationSubjectCacheInvalidation 同时处理目标主体权限与跨实例菜单同步。UserGroup 关系、字段策略、共享规则和临时授权仍有各自的事实源和失效路径;不要假设清除 Permission Cache 会顺带更新所有派生目录。详见配置、持久化与缓存。
8. 章节地图
| 想解决的问题 | 阅读入口 |
|---|---|
| 理解决策组合、缓存、DataScope 与审计 | 授权决策引擎 |
| 设计角色、授予权限和用户分配流程 | RBAC 管理与角色生命周期 |
| 使用金额、状态、关系与 Feature 权益 | ABAC、ReBAC 与 Feature |
| 装配生产 Store、理解表与失效范围 | 配置、持久化与缓存 |
| 接入字段安全、共享、分权和临时授权 | 高级治理、测试与运维 |
9. 源码审查入口
在 BitzOrcasVNext 根目录运行:
# 同时查看 Framework 决策核心与 Platform 策略管理,避免只读半个模块。rg -n "class AuthorizationDecisionService|class .*PolicyEvaluator|class .*Role" \ src/Framework/BitzOrcas.Application/Authorization src/Platform/Authorization -g '*.cs'
# 对比命令动作与权限目录,Grant/Revoke 应保持同构。rg -n "AuthorizationAction\.(Grant|Revoke)|role-permission\.(grant|revoke)|user-role\.revoke" \ src/Platform/Authorization -g '*.cs'
# 查看所有缓存失效调用;目标用户、租户、资源和 Feature 标签必须与变更对象一致。rg -n "InvalidateBy(User|Tenant|Resource)Async" \ src/Platform/Authorization src/Framework/BitzOrcas.Application/Authorization -g '*.cs'10. 商业交付最低证据
授权模块进入 GA 前至少需要证明:
- 权限目录和所有
IAuthorizedRequest派生权限码完全一致; - 分配或撤销角色后,目标用户的旧 Allow、旧 Deny 和菜单投影都立即失效;
- ABAC、ReBAC 和 Feature 的存储故障路径保持关闭式拒绝;
- DataScope 被调用方真正转换为查询过滤器,并有跨租户集成测试;
- 缓存命中仍重算当前 DataScope、写本次审计,Delegated 身份不跨有效期缓存;
- 字段安全、共享、分权与临时授权的 owner 执行面没有旁路;
- 双 ORM Store 契约、种子幂等、全局权限与 Feature 唯一性测试均通过。