Skip to content
bitzorcas
中EN

Guide

Authorization ABAC、ReBAC 与 Feature

用高额账单、案件协作和租户权益案例讲透属性规则、资源关系、Feature 覆盖、优先级、缓存与失败语义。

Last updated

RBAC 表达“这个主体通常能做什么”,ABAC 表达“在这些资源属性下还能不能做”,ReBAC 表达“主体与这个实例是否有业务关系”,Feature 表达“这个租户是否拥有这项能力”。四者解决的问题不同,最终由统一决策服务合并。

1. 用一个场景看清四种证据

Alice 具有 billing.invoice.approve;她正在审批一张金额 120,000、状态 Open、敏感级别 Confidential 的账单,并且是该账单的 Approver。租户没有启用受控 Feature。

Alice

RBAC
billing.invoice.approve = Allow

Invoice 120000
Confidential

ABAC
amount > 50000 = Deny

ReBAC
Approver + Approve = Allow

Tenant

Feature
Disabled = Deny

统一合并

Deny

两个 Allow 不能抵消任何一个 Deny。这不是投票系统,也没有“关系权限比属性规则更高”的隐式层级。

2. ABAC:只评估当前支持的资源属性

当前 ABAC 规则只包含单个条件。规则先按 Priority 升序排列,再过滤 Action,第一条条件命中的规则立即返回其 Verdict。

2.1 字段与运算符矩阵

ConditionField数值来源支持的运算符
Amount0ResourceDescriptor.Amount>、<、>=、<=、=、!=
Status1ResourceDescriptor.StatusEquals、NotEquals、In
Sensitivity2ResourceDescriptor.SensitivityEquals、NotEquals、In
CreateTime3当前没有 Resource 对应值枚举存在,但求值器固定返回 false
ConditionOperator数值说明
GreaterThan / LessThan0 / 1只对 Amount 有意义
GreaterThanOrEqual / LessThanOrEqual2 / 3只对 Amount 有意义
Equals / NotEquals4 / 5Amount 与文本字段均支持
In6只对文本字段;规则值是逗号分隔列表,逐项 Trim 后忽略大小写

Verdict 使用 Allow=0、Deny=1;ActionType 为空表示所有动作,否则使用 AuthorizationAction 的整数值。

2.2 创建高额账单拒绝规则

金额超过 50000 时拒绝 Approve
# ActionType=4 是 Approve,Field=0 是 Amount,Operator=0 是 GreaterThan,Verdict=1 是 Deny。
# 条件值采用 InvariantCulture 字符串;Priority 越小越先评估。
curl --fail-with-body --request POST "$API_URL/api/authorization/abac-rules" \
--header "Authorization: Bearer $ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"module": "billing",
"resourceType": "invoice",
"actionType": 4,
"conditionField": 0,
"conditionOperator": 0,
"conditionValue": "50000",
"verdict": 1,
"priority": 10
}'

金额按 InvariantCulture 解析。使用 50000.00,不要用 50,000元 或租户区域格式。规则创建成功后按有效租户失效统一 Permission 决策缓存;否则已有 Allow 可能继续绕过新 Deny。

2.3 第一条命中,不是 Deny 自动优先

ABAC Evaluator 内部与全局合并规则不同。ABAC 内部只返回第一条命中规则:

ABAC 优先级的安全测试
[Fact]
public async Task Lower_Priority_Number_Should_Deny_Before_Broad_Allow()
{
// 两条规则都命中;Priority=10 必须先于 Priority=100。
// 宽泛 Allow 放在后面,证明 ABAC 内部使用第一命中而非收集全部规则。
ruleStore.Rules =
[
Rule("deny-high-value", amount: "50000", PolicyVerdict.Deny, priority: 10),
Rule("allow-general", amount: "10000", PolicyVerdict.Allow, priority: 100),
];
var result = await evaluator.EvaluateAsync(
User(),
new ResourceDescriptor("billing", "invoice", Amount: 120_000m),
AuthorizationAction.Approve,
CancellationToken.None);
result.Verdict.Should().Be(PolicyVerdict.Deny);
result.Reason.Should().Be("abac:rule:deny-high-value");
}

同一 Priority 的相互冲突规则不应依赖数据库返回顺序。用唯一、可审查的优先级表达策略先后,并用测试锁定关键规则。

2.4 ABAC 故障语义

  • 没有规则:Neutral;
  • 有规则但条件都不命中:Neutral;
  • 规则 Store 抛非取消异常:Deny,理由 abac:store-unavailable;
  • 取消:原样传播;
  • 无效 Amount 文本或缺少资源 Amount:该条件不命中。

3. ReBAC Lite:关系必须同时允许当前动作

ReBAC 只参与 Case、Client、Billing 三种资源类型,比较忽略大小写。还必须有 UserId 和 ResourceId;集合级请求不会走 ReBAC。

3.1 关系动作白名单

RelationType允许动作
Owner除 Read 外的 View、Create、Update、Delete、Approve、Reject、Export、Import、Assign、Transfer、Search、Manage
CollaboratorView、Update、Search
AgentView、Create、Update、Export、Import、Assign、Transfer、Search
ApproverView、Approve、Reject
未知值或大小写不一致不允许

RelationStore 先在可信租户内查找 Active、未删除的关系,再调用 ReBacRelationPolicy.Allows。关系存在但动作不在白名单时返回 false,Evaluator 将其解释为 Neutral,而不是 Deny。

为实例级业务请求提供完整 ReBAC 事实
var resource = new ResourceDescriptor(
Module: "cases",
ResourceType: "Case",
ResourceId: caseFile.Id,
TenantId: currentTenant.Id,
OwnerUserId: caseFile.OwnerUserId,
DepartmentId: caseFile.DepartmentId,
Status: caseFile.Status,
Sensitivity: caseFile.Sensitivity);
// Approver 关系可以允许 Approve,但不能允许 Delete。
// TenantId 与 ResourceId 来自服务端资源,阻止客户端伪造关系查询键。
var decision = await authorization.EvaluateAsync(
currentUser.User,
resource,
AuthorizationAction.Approve,
cancellationToken);

3.2 ReBAC 缓存与写侧责任

缓存键包含 TenantId、ResourceType、ResourceId、UserId 与 Action,正负关系都使用 Short 策略缓存,并以资源标签写入。关系变更后必须调用 IReBacCache.InvalidateByResourceAsync。

当前 Authorization 模块只有关系查询 Store,没有创建、更新、删除关系的公开 Command/Endpoint,也没有在模块内调用资源级失效。资源 owner 如果直接维护 SysResourceRelation,必须同时拥有一致的写协议和缓存失效;否则新协作者看不到权限,已移除协作者还可能命中旧 Allow。

4. Feature:全局默认与租户覆盖

Feature 不是 RBAC 权限别名。权限回答“这个主体可以做什么”,Feature 回答“这个租户是否启用了这个模块权益”。

否是是否是否

resource.Module

存在硬编码映射?

Neutral

Feature 缓存命中?

Enabled=Allow
Disabled=Deny

租户 Override 存在?

全局 DefaultState

当前 Evaluator 只映射:

ModuleFeatureCode
ticketsplatform.tickets
chatplatform.chat
workflowplatform.workflow
reportingplatform.reporting

其他模块即使在 Feature 定义表中存在,例如 notifications、webhooks、files,也会在该 Evaluator 返回 Neutral。当前种子同时包含 platform.tickets、platform.chat、platform.workflow 与 platform.reporting,Demo 租户覆盖也显式登记 workflow/reporting;缺少定义时仍按 false 关闭式拒绝。

4.1 更新租户 Feature

为当前租户启用工单功能
# UpdateFeatureState 会根据定义的 IsTenantScoped 决定写租户覆盖还是全局 DefaultState。
curl --fail-with-body --request PUT \
"$API_URL/api/authorization/features/platform.tickets" \
--header "Authorization: Bearer $ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{ "isEnabled": true }'
  • IsTenantScoped=true:Upsert 当前 TenantId + FeatureCode 的覆盖行,不修改全局默认;
  • IsTenantScoped=false:修改全局 DefaultState;
  • 成功后发布 FeatureChangedIntegrationEvent;租户覆盖按当前租户失效,全局默认调用 InvalidateAllAsync。

非租户 Feature 的全局更新会清除所有 Feature 缓存。应保留多租户、多实例收敛测试,防止后续把全局范围错误收窄为操作者租户。

4.2 Feature 故障语义

  • 映射不存在:Neutral;
  • 定义不存在:FeatureStore 返回 false,Evaluator Deny;
  • Store 查询异常:Store 记录告警并返回 false,Evaluator Deny;
  • CoreRuntime 未装配生产 Provider:UnavailableFeatureDecisionProvider 固定 false;
  • Feature 缓存异常或自定义 Provider 抛异常:Evaluator 当前不捕获,调用失败。

5. 组合策略测试

不要只为每个 Evaluator 写孤立单测。至少增加一组统一决策集成测试:

宽泛 RBAC Allow 不能越过属性与权益拒绝
[Theory]
[InlineData(120000, true, false)]
[InlineData(1000, false, false)]
[InlineData(1000, true, true)]
public async Task Invoice_Approval_Should_Combine_All_Policies(
decimal amount,
bool featureEnabled,
bool expectedAllowed)
{
// 用户始终有 RBAC 权限;测试只改变资源属性和租户 Feature。
// 三组数据分别固定 ABAC Deny、Feature Deny 和最终 Allow。
var user = UserWithPermission("billing.invoice.approve");
featureProvider.Set("platform.billing", featureEnabled);
abacRules.SetHighValueDeny(threshold: 50_000m);
var decision = await service.EvaluateAsync(
user,
new ResourceDescriptor("billing", "invoice", Amount: amount),
AuthorizationAction.Approve,
CancellationToken.None);
decision.IsAllowed.Should().Be(expectedAllowed);
}

该示例展示应有的组合测试形状;当前 Feature Evaluator 没有 billing → platform.billing 映射,落地前要么改用已有映射的工单案例,要么先通过受评审的映射扩展实现业务需求。

6. 上线前策略审查

  1. Resource 的租户、金额、状态、敏感级别是否来自服务端可信数据?
  2. ABAC 优先级是否唯一、可解释,是否有第一命中回归测试?
  3. 是否误用了 CreateTime、错误字段/运算符组合或区域化数值?
  4. ReBAC 关系写入由谁拥有,变更后如何按 Resource 失效?
  5. Feature 映射和 Seed 是否成对存在,租户覆盖与全局状态是否分别测试?
  6. 明确 Deny、全 Neutral、Store Failure、缓存异常和取消是否都有独立预期?

上一页:RBAC 管理与角色生命周期 · 下一篇:配置、持久化与缓存

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%