授权模型技能Skill authorization-models

这个技能提供全面的授权模型和访问控制模式指导,包括RBAC、ABAC、ACL、ReBAC和策略即代码。适用于设计权限系统、实现访问控制、选择授权策略,以及应用最小权限原则和多租户授权。关键词:授权、访问控制、RBAC、ABAC、安全、权限系统、策略即代码、Zanzibar、OPA、Casbin。

身份认证 0 次安装 0 次浏览 更新于 3/11/2026

name: 授权模型 description: 全面的授权指导,涵盖RBAC、ABAC、ACL、ReBAC和策略即代码模式。用于设计权限系统、实现访问控制或选择授权策略。 allowed-tools: Read, Glob, Grep, Task

授权模型技能

概述

这个技能提供关于授权模型和访问控制模式的全面指导。授权确定已认证用户在系统内可以执行的操作。

关键原则: 授权应该是声明式的、集中化的和可审计的。

何时使用此技能

  • 从零开始设计权限系统
  • 在RBAC、ABAC、ACL或ReBAC之间选择
  • 使用OPA实现策略即代码
  • 从简单角色检查迁移到细粒度授权
  • 实现最小权限原则
  • 设计多租户授权
  • 构建Zanzibar风格的权限系统

授权模型比较

模型 最佳适用 复杂性 可扩展性 灵活性
ACL 文件系统、简单资源
RBAC 企业应用、清晰的工作角色
ABAC 复杂策略、动态规则
ReBAC 社交图、文档共享 中-高 非常高

快速决策树

需要授权模型?
├── 简单的资源所有权?
│   └── ACL(访问控制列表)
├── 清晰的组织角色?
│   └── RBAC(基于角色的访问控制)
├── 复杂的、上下文相关的规则?
│   └── ABAC(基于属性的访问控制)
└── 基于关系的访问(共享、层次结构)?
    └── ReBAC(基于关系的访问控制)

基于角色的访问控制(RBAC)

核心概念

/// <summary>
/// RBAC的细粒度权限。
/// </summary>
[Flags]
public enum Permission
{
    None = 0,
    Read = 1,
    Create = 2,
    Update = 4,
    Delete = 8,
    Admin = 16,
    Approve = 32,
    Publish = 64,

    // 常见组合
    ReadWrite = Read | Update,
    Editor = Read | Create | Update,
    FullAccess = Read | Create | Update | Delete | Admin
}

/// <summary>
/// 具有关联权限的角色。
/// </summary>
public sealed record Role(string Name, Permission Permissions, string Description = "");

/// <summary>
/// 标准角色定义。
/// </summary>
public static class StandardRoles
{
    public static readonly Role Viewer = new("viewer", Permission.Read, "只读访问");
    public static readonly Role Editor = new("editor", Permission.Editor, "可以创建和编辑内容");
    public static readonly Role Admin = new("admin", Permission.FullAccess, "完全管理访问");

    public static readonly IReadOnlyDictionary<string, Role> All = new Dictionary<string, Role>
    {
        [Viewer.Name] = Viewer,
        [Editor.Name] = Editor,
        [Admin.Name] = Admin
    };
}

RBAC实现

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

/// <summary>
/// 简单的RBAC授权服务。
/// </summary>
public sealed class RbacAuthorizer
{
    private readonly Dictionary<string, HashSet<string>> _userRoles = new();

    public void AssignRole(string userId, string role)
    {
        if (!_userRoles.TryGetValue(userId, out var roles))
        {
            roles = new HashSet<string>();
            _userRoles[userId] = roles;
        }
        roles.Add(role);
    }

    public bool HasPermission(string userId, Permission permission)
    {
        if (!_userRoles.TryGetValue(userId, out var userRoles))
            return false;

        foreach (var roleName in userRoles)
        {
            if (StandardRoles.All.TryGetValue(roleName, out var role) &&
                role.Permissions.HasFlag(permission))
            {
                return true;
            }
        }
        return false;
    }

    public bool HasRole(string userId, string role) =>
        _userRoles.TryGetValue(userId, out var roles) && roles.Contains(role);
}

/// <summary>
/// ASP.NET Core权限授权需求。
/// </summary>
public sealed class PermissionRequirement(Permission permission) : IAuthorizationRequirement
{
    public Permission Permission { get; } = permission;
}

/// <summary>
/// 基于权限的授权处理器。
/// </summary>
public sealed class PermissionHandler(RbacAuthorizer authorizer) : AuthorizationHandler<PermissionRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        PermissionRequirement requirement)
    {
        var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);
        if (userId is not null && authorizer.HasPermission(userId, requirement.Permission))
        {
            context.Succeed(requirement);
        }
        return Task.CompletedTask;
    }
}

// 使用属性
[Authorize(Policy = "RequireCreate")]
[HttpPost("articles")]
public IActionResult CreateArticle([FromBody] ArticleDto article)
{
    // 只有具有CREATE权限的用户可以访问
    return Ok();
}

分层RBAC

/// <summary>
/// 支持继承的角色。
/// </summary>
public sealed class HierarchicalRole(
    string name,
    Permission directPermissions,
    HierarchicalRole? parent = null)
{
    public string Name { get; } = name;
    public HierarchicalRole? Parent { get; } = parent;

    /// <summary>
    /// 获取所有权限,包括从父角色继承的。
    /// </summary>
    public Permission AllPermissions
    {
        get
        {
            var permissions = directPermissions;
            var current = Parent;
            while (current is not null)
            {
                permissions |= current.AllPermissions;
                current = current.Parent;
            }
            return permissions;
        }
    }
}

// 角色层次:admin > editor > viewer
var viewerRole = new HierarchicalRole("viewer", Permission.Read);
var editorRole = new HierarchicalRole("editor", Permission.Create | Permission.Update, viewerRole);
var adminRole = new HierarchicalRole("admin", Permission.Delete | Permission.Admin, editorRole);

// adminRole.AllPermissions 包括从父角色继承的所有权限

基于属性的访问控制(ABAC)

核心概念

using System.Collections.Immutable;

/// <summary>
/// 访问决策的上下文。
/// </summary>
public sealed record AccessRequest(
    ImmutableDictionary<string, object> Subject,     // 谁在请求
    ImmutableDictionary<string, object> Resource,    // 他们在访问什么
    string Action,                                    // 他们想做什么
    ImmutableDictionary<string, object> Environment  // 上下文(时间、位置等)
)
{
    public T GetSubjectAttribute<T>(string key, T defaultValue = default!) =>
        Subject.TryGetValue(key, out var value) && value is T typed ? typed : defaultValue;

    public T GetResourceAttribute<T>(string key, T defaultValue = default!) =>
        Resource.TryGetValue(key, out var value) && value is T typed ? typed : defaultValue;

    public T GetEnvironmentAttribute<T>(string key, T defaultValue = default!) =>
        Environment.TryGetValue(key, out var value) && value is T typed ? typed : defaultValue;
}

/// <summary>
/// 策略效果类型。
/// </summary>
public enum PolicyEffect { Permit, Deny }

/// <summary>
/// 基于属性的策略评估。
/// </summary>
public sealed class AbacPolicy(
    string name,
    Func<AccessRequest, bool> condition,
    PolicyEffect effect = PolicyEffect.Permit)
{
    public string Name { get; } = name;

    /// <summary>
    /// 如果条件匹配返回效果,否则返回null。
    /// </summary>
    public PolicyEffect? Evaluate(AccessRequest request) =>
        condition(request) ? effect : null;
}

ABAC策略示例

// 策略:只有经理可以批准超过1000美元的支出
var managerApprovalPolicy = new AbacPolicy(
    name: "manager_approval",
    condition: req =>
        req.Action == "approve" &&
        req.GetResourceAttribute<string>("type") == "expense" &&
        req.GetResourceAttribute<decimal>("amount") > 1000 &&
        req.GetSubjectAttribute<string>("role") == "manager"
);

// 策略:用户只能访问自己部门的数据
var departmentIsolationPolicy = new AbacPolicy(
    name: "department_isolation",
    condition: req =>
        req.GetSubjectAttribute<string>("department") ==
        req.GetResourceAttribute<string>("department")
);

// 策略:非工作时间无访问
var businessHoursPolicy = new AbacPolicy(
    name: "business_hours",
    condition: req =>
    {
        var now = DateTime.Now;
        var hour = now.Hour;
        var dayOfWeek = now.DayOfWeek;
        return hour >= 9 && hour <= 17 &&
               dayOfWeek != DayOfWeek.Saturday &&
               dayOfWeek != DayOfWeek.Sunday;
    }
);

// 策略:拒绝来自不信任网络的访问
var networkPolicy = new AbacPolicy(
    name: "trusted_network",
    condition: req =>
        req.GetEnvironmentAttribute<string>("ip_address", "")
           .StartsWith("10.0.", StringComparison.Ordinal)
);

ABAC策略引擎

/// <summary>
/// 策略决策点(PDP)使用拒绝覆盖算法。
/// </summary>
public sealed class AbacEngine(PolicyEffect defaultEffect = PolicyEffect.Deny)
{
    private readonly List<AbacPolicy> _policies = [];

    public void AddPolicy(AbacPolicy policy) => _policies.Add(policy);

    /// <summary>
    /// 评估所有策略。拒绝覆盖组合算法。
    /// </summary>
    public bool Evaluate(AccessRequest request)
    {
        var permitFound = false;

        foreach (var policy in _policies)
        {
            var effect = policy.Evaluate(request);
            if (effect == PolicyEffect.Deny)
            {
                return false;  // 显式拒绝总是胜出
            }
            if (effect == PolicyEffect.Permit)
            {
                permitFound = true;
            }
        }

        return permitFound || defaultEffect == PolicyEffect.Permit;
    }
}

// 使用
var engine = new AbacEngine();
engine.AddPolicy(departmentIsolationPolicy);
engine.AddPolicy(businessHoursPolicy);

var request = new AccessRequest(
    Subject: ImmutableDictionary.CreateRange(new Dictionary<string, object>
    {
        ["user_id"] = "123",
        ["department"] = "engineering",
        ["role"] = "developer"
    }),
    Resource: ImmutableDictionary.CreateRange(new Dictionary<string, object>
    {
        ["id"] = "doc-456",
        ["department"] = "engineering",
        ["type"] = "document"
    }),
    Action: "read",
    Environment: ImmutableDictionary.CreateRange(new Dictionary<string, object>
    {
        ["ip_address"] = "10.0.1.50",
        ["time"] = DateTime.UtcNow
    })
);

if (engine.Evaluate(request))
{
    // 访问授予
}

访问控制列表(ACL)

简单ACL实现

/// <summary>
/// Unix风格的ACL权限。
/// </summary>
[Flags]
public enum AclPermission
{
    None = 0,
    Read = 1,
    Write = 2,
    Execute = 4,
    Delete = 8,
    Admin = 16,

    // 常见组合
    ReadWrite = Read | Write,
    Full = Read | Write | Execute | Delete | Admin
}

/// <summary>
/// ACL条目的主体类型。
/// </summary>
public enum PrincipalType { User, Group }

/// <summary>
/// 访问控制条目。
/// </summary>
public sealed class AclEntry(string principal, AclPermission permissions, PrincipalType principalType = PrincipalType.User)
{
    public string Principal { get; } = principal;
    public AclPermission Permissions { get; set; } = permissions;
    public PrincipalType PrincipalType { get; } = principalType;
}

/// <summary>
/// 资源的访问控制列表。
/// </summary>
public sealed class Acl(string resourceId, string owner)
{
    public string ResourceId { get; } = resourceId;
    public string Owner { get; } = owner;
    private readonly Dictionary<string, AclEntry> _entries = new();

    /// <summary>
    /// 授予权限给主体。
    /// </summary>
    public void Grant(string principal, AclPermission permissions, PrincipalType principalType = PrincipalType.User)
    {
        if (_entries.TryGetValue(principal, out var entry))
        {
            entry.Permissions |= permissions;
        }
        else
        {
            _entries[principal] = new AclEntry(principal, permissions, principalType);
        }
    }

    /// <summary>
    /// 从主体撤销权限。
    /// </summary>
    public void Revoke(string principal, AclPermission permissions)
    {
        if (_entries.TryGetValue(principal, out var entry))
        {
            entry.Permissions &= ~permissions;
            if (entry.Permissions == AclPermission.None)
            {
                _entries.Remove(principal);
            }
        }
    }

    /// <summary>
    /// 检查主体是否有权限。
    /// </summary>
    public bool Check(string principal, AclPermission permission, ISet<string>? userGroups = null)
    {
        // 所有者有完全访问
        if (principal == Owner)
        {
            return true;
        }

        // 检查直接用户条目
        if (_entries.TryGetValue(principal, out var entry) &&
            entry.Permissions.HasFlag(permission))
        {
            return true;
        }

        // 检查组条目
        if (userGroups is not null)
        {
            foreach (var group in userGroups)
            {
                if (_entries.TryGetValue(group, out var groupEntry) &&
                    groupEntry.PrincipalType == PrincipalType.Group &&
                    groupEntry.Permissions.HasFlag(permission))
                {
                    return true;
                }
            }
        }

        return false;
    }
}

ACL使用示例

// 创建文档的ACL
var docAcl = new Acl(resourceId: "doc-123", owner: "alice");

// 授予权限
docAcl.Grant("bob", AclPermission.ReadWrite);
docAcl.Grant("engineering", AclPermission.Read, PrincipalType.Group);
docAcl.Grant("charlie", AclPermission.Read);

// 检查权限
docAcl.Check("alice", AclPermission.Delete);   // True (所有者)
docAcl.Check("bob", AclPermission.Write);      // True (显式授予)
docAcl.Check("bob", AclPermission.Delete);     // False (未授予)
docAcl.Check("dave", AclPermission.Read,
             userGroups: new HashSet<string> { "engineering" });  // True (组成员资格)

基于关系的访问控制(ReBAC)

Zanzibar风格模型

/// <summary>
/// Zanzibar风格的关系元组(对象、关系、主体)。
/// </summary>
public readonly record struct Relationship(
    string ObjectType,
    string ObjectId,
    string Relation,
    string SubjectType,
    string SubjectId,
    string? SubjectRelation = null)  // 用于用户集
{
    public override string ToString()
    {
        var subject = $"{SubjectType}:{SubjectId}";
        if (SubjectRelation is not null)
        {
            subject += $"#{SubjectRelation}";
        }
        return $"{ObjectType}:{ObjectId}#{Relation}@{subject}";
    }
}

/// <summary>
/// 简单的ReBAC实现(受Zanzibar启发)。
/// </summary>
public sealed class ReBac
{
    // 存储关系: (object_type, object_id, relation) -> 主体集合
    private readonly Dictionary<(string ObjectType, string ObjectId, string Relation),
                                HashSet<(string SubjectType, string SubjectId, string? SubjectRelation)>> _tuples = new();

    // 关系定义与计算关系
    // object_type -> relation -> 父关系集合用于继承
    private readonly Dictionary<string, Dictionary<string, HashSet<string>>> _relationConfig = new();

    /// <summary>
    /// 定义关系及其继承。
    /// </summary>
    public void DefineRelation(string objectType, string relation, IEnumerable<string>? inheritsFrom = null)
    {
        if (!_relationConfig.TryGetValue(objectType, out var relations))
        {
            relations = new Dictionary<string, HashSet<string>>();
            _relationConfig[objectType] = relations;
        }
        relations[relation] = inheritsFrom?.ToHashSet() ?? [];
    }

    /// <summary>
    /// 写入关系元组。
    /// </summary>
    public void Write(Relationship rel)
    {
        var key = (rel.ObjectType, rel.ObjectId, rel.Relation);
        if (!_tuples.TryGetValue(key, out var subjects))
        {
            subjects = [];
            _tuples[key] = subjects;
        }
        subjects.Add((rel.SubjectType, rel.SubjectId, rel.SubjectRelation));
    }

    /// <summary>
    /// 删除关系元组。
    /// </summary>
    public void Delete(Relationship rel)
    {
        var key = (rel.ObjectType, rel.ObjectId, rel.Relation);
        if (_tuples.TryGetValue(key, out var subjects))
        {
            subjects.Remove((rel.SubjectType, rel.SubjectId, rel.SubjectRelation));
        }
    }

    /// <summary>
    /// 检查主体是否与对象有关系。
    /// </summary>
    public bool Check(string objectType, string objectId, string relation,
                      string subjectType, string subjectId)
    {
        var key = (objectType, objectId, relation);

        // 直接检查
        if (_tuples.TryGetValue(key, out var subjects) &&
            subjects.Contains((subjectType, subjectId, null)))
        {
            return true;
        }

        // 检查继承的关系
        if (_relationConfig.TryGetValue(objectType, out var relations) &&
            relations.TryGetValue(relation, out var inherited))
        {
            foreach (var parentRelation in inherited)
            {
                if (Check(objectType, objectId, parentRelation, subjectType, subjectId))
                {
                    return true;
                }
            }
        }

        // 检查用户集重写(例如,folder:123#viewer@document:456#parent)
        if (_tuples.TryGetValue(key, out var tupleSubjects))
        {
            foreach (var (subjType, subjId, subjRel) in tupleSubjects)
            {
                if (subjRel is not null)
                {
                    // 这是一个用户集 - 检查用户在引用对象上是否有该关系
                    if (Check(subjType, subjId, subjRel, subjectType, subjectId))
                    {
                        return true;
                    }
                }
            }
        }

        return false;
    }
}

ReBAC使用示例(Google Drive风格)

// 初始化ReBAC
var rebac = new ReBac();

// 定义关系层次
// 编辑者也可以查看(编辑者继承自查看者)
rebac.DefineRelation("document", "viewer", null);
rebac.DefineRelation("document", "editor", ["viewer"]);
rebac.DefineRelation("document", "owner", ["editor"]);

rebac.DefineRelation("folder", "viewer", null);
rebac.DefineRelation("folder", "editor", ["viewer"]);
rebac.DefineRelation("folder", "owner", ["editor"]);

// 文件夹查看者是文档查看者(文档从父文件夹继承)
rebac.DefineRelation("document", "parent", null);

// 创建关系
// Alice拥有文件夹"projects"
rebac.Write(new Relationship("folder", "projects", "owner", "user", "alice"));

// Bob是文件夹"projects"的编辑者
rebac.Write(new Relationship("folder", "projects", "editor", "user", "bob"));

// 文档"spec"在文件夹"projects"中
// 任何可以查看文件夹的人都可以查看文档
rebac.Write(new Relationship("document", "spec", "parent", "folder", "projects",
                             SubjectRelation: "viewer"));

// Charlie有直接查看文档的访问权限
rebac.Write(new Relationship("document", "spec", "viewer", "user", "charlie"));

// 检查权限
rebac.Check("folder", "projects", "owner", "user", "alice");    // True
rebac.Check("folder", "projects", "viewer", "user", "alice");   // True (owner->editor->viewer)
rebac.Check("folder", "projects", "editor", "user", "bob");     // True
rebac.Check("document", "spec", "viewer", "user", "bob");       // True (folder editor->viewer)
rebac.Check("document", "spec", "editor", "user", "charlie");   // False (只有查看者)

策略即代码与Open Policy Agent(OPA)

Rego策略基础

# policy.rego
package authz

import future.keywords.if
import future.keywords.in

# 默认拒绝
default allow := false

# 如果用户有所需权限则允许
allow if {
    user_has_permission[input.action]
}

# 基于角色的用户权限
user_has_permission[permission] if {
    some role in input.user.roles
    some permission in role_permissions[role]
}

# 角色到权限映射
role_permissions := {
    "admin": ["read", "write", "delete", "admin"],
    "editor": ["read", "write"],
    "viewer": ["read"],
}

# 资源特定规则
allow if {
    input.action == "read"
    input.resource.public == true
}

# 所有者总是可以访问其资源
allow if {
    input.resource.owner == input.user.id
}

.NET中的OPA集成

using System.Text.Json;
using System.Text.Json.Serialization;

/// <summary>
/// Open Policy Agent客户端。
/// </summary>
public sealed class OpaClient(HttpClient httpClient, string opaUrl = "http://localhost:8181") : IDisposable
{
    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
    };

    /// <summary>
    /// 查询OPA以获取策略决策。
    /// </summary>
    public async Task<bool> CheckAsync(string policyPath, object inputData, CancellationToken cancellationToken = default)
    {
        var url = $"{opaUrl}/v1/data/{policyPath}";
        var request = new OpaRequest(inputData);

        var response = await httpClient.PostAsJsonAsync(url, request, JsonOptions, cancellationToken);
        response.EnsureSuccessStatusCode();

        var result = await response.Content.ReadFromJsonAsync<OpaResponse<bool>>(JsonOptions, cancellationToken);
        return result?.Result ?? false;
    }

    /// <summary>
    /// 运行任意Rego查询。
    /// </summary>
    public async Task<JsonElement?> QueryAsync(string query, object inputData, CancellationToken cancellationToken = default)
    {
        var url = $"{opaUrl}/v1/query";
        var request = new { query, input = inputData };

        var response = await httpClient.PostAsJsonAsync(url, request, JsonOptions, cancellationToken);
        response.EnsureSuccessStatusCode();

        var result = await response.Content.ReadFromJsonAsync<OpaResponse<JsonElement>>(JsonOptions, cancellationToken);
        return result?.Result;
    }

    public void Dispose() => httpClient.Dispose();

    private sealed record OpaRequest([property: JsonPropertyName("input")] object Input);
    private sealed record OpaResponse<T>([property: JsonPropertyName("result")] T? Result);
}

// 使用
using var httpClient = new HttpClient();
var opa = new OpaClient(httpClient);

var inputData = new
{
    user = new
    {
        id = "alice",
        roles = new[] { "editor" }
    },
    action = "write",
    resource = new
    {
        id = "doc-123",
        owner = "bob",
        @public = false
    }
};

if (await opa.CheckAsync("authz/allow", inputData))
{
    // 访问授予
}

使用ABAC策略的OPA

# abac_policy.rego
package abac

import future.keywords.if
import future.keywords.in

default allow := false

# 基于时间的访问
allow if {
    input.action == "read"
    is_business_hours
    user_in_same_department
}

is_business_hours if {
    time.hour(time.now_ns()) >= 9
    time.hour(time.now_ns()) <= 17
    time.weekday(time.now_ns()) < 5
}

user_in_same_department if {
    input.user.department == input.resource.department
}

# 支出批准规则
allow if {
    input.action == "approve"
    input.resource.type == "expense"
    can_approve_amount
}

can_approve_amount if {
    input.resource.amount <= 1000
    "employee" in input.user.roles
}

can_approve_amount if {
    input.resource.amount <= 10000
    "manager" in input.user.roles
}

can_approve_amount if {
    "director" in input.user.roles
}

授权库和工具

比较

工具 模型 语言 最佳适用
OPA ABAC/策略 Rego Kubernetes、微服务
Casbin RBAC/ABAC/ACL 多语言 通用目的
Oso ABAC/ReBAC Polar 应用嵌入
SpiceDB ReBAC (Zanzibar) gRPC 大规模权限
Cerbos ABAC YAML 云原生应用

Casbin快速入门

using Casbin;

// 定义模型 (model.conf)
/*
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[role_definition]
g = _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
*/

// 创建执行器
var enforcer = new Enforcer("model.conf", "policy.csv");

// 检查权限
if (enforcer.Enforce("alice", "data1", "read"))
{
    // 访问授予
}

// 动态添加策略
await enforcer.AddPolicyAsync("bob", "data2", "write");
await enforcer.AddGroupingPolicyAsync("alice", "admin");

// 对于ASP.NET Core集成,使用Casbin.AspNetCore
// services.AddCasbinAuthorization(options =>
// {
//     options.DefaultModelPath = "model.conf";
//     options.DefaultPolicyPath = "policy.csv";
// });

最佳实践

设计原则

  1. 最小权限原则:授予必要的最小权限
  2. 职责分离:敏感操作需要多方参与
  3. 深度防御:在多个级别层叠授权检查
  4. 安全失败:当授权状态不明确时拒绝访问
  5. 集中逻辑:单一策略决策点(PDP)
  6. 审计一切:记录所有授权决策

实现指南

// 好:集中化授权
public interface IAuditLogger
{
    Task LogAsync(string userId, string resourceId, string action, string decision, object? context);
}

/// <summary>
/// 集中化授权服务 - 所有决策的单一入口点。
/// </summary>
public sealed class AuthorizationService(AbacEngine engine, IAuditLogger auditLogger)
{
    /// <summary>
    /// 所有授权决策的单一入口点。
    /// </summary>
    public async Task<bool> AuthorizeAsync(AccessRequest request)
    {
        var decision = engine.Evaluate(request);

        // 总是审计
        await auditLogger.LogAsync(
            userId: request.GetSubjectAttribute<string>("user_id") ?? "unknown",
            resourceId: request.GetResourceAttribute<string>("id") ?? "unknown",
            action: request.Action,
            decision: decision ? "permit" : "deny",
            context: request.Environment
        );

        return decision;
    }
}

// 差:分散的授权检查
public Document? GetDocument(string docId, User user)
{
    var doc = _repository.GetById(docId);
    // 授权逻辑到处重复 - 避免此模式!
    if (user.Role == "admin" || doc?.OwnerId == user.Id)
    {
        return doc;
    }
    return null;
}

常见陷阱

陷阱 问题 解决方案
硬编码角色 不灵活,难以更改 使用基于权限的检查
缺少负面测试 虚假安全感 明确测试拒绝案例
仅客户端 容易被绕过 总是在服务器端强制执行
过于复杂策略 难以审计 保持策略简单、可组合
无审计跟踪 无法调查事件 记录所有决策

相关技能

  • authentication-patterns - 授权前验证身份
  • api-security - 在API边界应用授权
  • zero-trust - 永不信任、总是验证的架构
  • secure-coding - 防止授权绕过漏洞

参考资料

深度探讨:

安全清单

设计阶段

  • [ ] 基于需求选择授权模型
  • [ ] 应用最小权限原则
  • [ ] 记录角色/权限
  • [ ] 识别边界情况(继承、委托)

实现阶段

  • [ ] 授权集中化(单一PDP)
  • [ ] 服务器端强制执行
  • [ ] 所有端点一致授权检查
  • [ ] 实现审计日志

测试阶段

  • [ ] 正面测试(允许的访问有效)
  • [ ] 负面测试(拒绝的访问被阻止)
  • [ ] 特权提升测试
  • [ ] 角色层次测试

运营阶段

  • [ ] 定期权限审查
  • [ ] 删除未使用的角色/权限
  • [ ] 监控审计日志
  • [ ] 授权失败的应急响应计划