XiHan Framework 处于预览阶段

查看获取方式
跳转到内容

XiHan.Framework.Security.Abstractions ​

安全的抽象包:凭据与认证处理器、授权要求与策略目录、RBAC 授予存储与检查器、收窄过滤器与授予来源、Authenticate / Authorize 阶段中间件,以及条件引擎与 IP 归属地契约。能力 security.authentication@1、security.authorization@1、security.ip-region@1 由安全 Provider 提供。

项内容
包 IDXiHan.Framework.Security.Abstractions
环层Ring 2(抽象)
能力定义 security.authentication@1、security.authorization@1、security.ip-region@1(SecurityCapabilities),本包不提供
特性入口—
配置节XiHan:Security(SecurityOptions,由 Provider 读取)
依赖XiHan.Framework.Kernel、XiHan.Framework.Localization.Abstractions、Microsoft.Extensions.Configuration.Abstractions、Microsoft.Extensions.DependencyInjection.Abstractions
AOT兼容
稳定性Stable;Conditions 与 Network 命名空间为 Preview

概述 ​

主体、租户、授权请求、策略接口与权限登记这些原语在内核的 XiHan.Framework.Kernel.Security 命名空间(Principal、SecurityContext、TenantContext、AuthorizationRequest、AuthorizationResult、IPolicy、PermissionRegistry、PermissionDefinition、ResourceName),本包在其上定义认证与授权的组件。

认证:宿主把协议凭据放进调用上下文,AuthenticationMiddleware 交给方案匹配的 IAuthenticationHandler,成功写入主体,失败以结果返回。授权:调用带 AuthorizationRequirement 时,AuthorizationMiddleware 按名在 PolicyCatalog 取策略判定;permission:<code> 在权限码已登记时映射为 PermissionPolicy,由 PermissionChecker 按用户与角色授予之并集判定,再经授予来源补充、经过滤器收窄。认证与授权失败都以 Result 返回,宿主翻译成 401 / 403;处理器内部的基础设施故障抛异常。

条件引擎(Conditions)与 IP 归属地契约(Network)不接入任何管道阶段,应用在自己的策略、过滤器或中间件里使用。

何时使用 ​

安装与启用 ​

bash
dotnet add package XiHan.Framework.Security.Abstractions --prerelease

当前是预览版,安装要带 --prerelease;从源码或本地包源引用见快速上手。本包没有特性入口,Profiles.Standard 与 Profiles.Full 已经引用它。

配置 ​

SecurityOptions.FromConfiguration 读取 XiHan:Security 下的两个键:

键类型缺省说明
RequireAuthenticationboolfalse为真时匿名调用在 Authenticate 阶段被拒绝(SECURITY_DENIED_001)
RoleClaimstringrole承载角色的声明类型,不能为空
json
{
  "XiHan": {
    "Security": { "RequireAuthentication": false, "RoleClaim": "role" }
  }
}

多个安全 Provider 同时装配时,SecurityOptions 只注册一次。

主要类型 ​

类型说明
Authentication.AuthenticationCredential凭据:Scheme(不区分大小写)与 Value;TryParse("<scheme> <value>");ToString() 隐藏值
Authentication.IAuthenticationHandler某一方案的处理器:Scheme、AuthenticateAsync → Result<Principal>
Authentication.AuthenticationMiddlewareAuthenticate 阶段的中间件
Authorization.AuthorizationRequirement授权要求:Policy、Resource;Authenticated、Permission(code, resource?)
Authorization.PolicyCatalog策略目录:Find(name)、Names;策略名重复在构造时抛出 ArgumentException
Authorization.AuthenticatedPolicy策略 authenticated
Authorization.PermissionPolicy权限策略:Prefix(permission:)、NameFor(code)、CodeOf(name)
Authorization.AuthorizationMiddlewareAuthorize 阶段的中间件;策略判定失败以该失败结束调用(ADR-0071)
Permissions.GrantSubject授予的主体:ForUser(id)、ForRole(name),种类常量 User、Role
Permissions.PermissionGrant一条授予:主体、权限码(All = *)、租户(null 为全局)
Permissions.IPermissionGrantStore授予存储:GetGrantsAsync(subject, tenantId) → Result<IReadOnlyCollection<string>>,全局授予加该租户的授予;存储不可达是 SECURITY_UNAVAILABLE_001,不是空集合
Permissions.IPermissionChecker / PermissionCheckerRBAC 检查器:HasPermissionAsync → Result<bool>,存储、来源、过滤器的失败原样返回
Permissions.IPermissionFilter授予命中后的收窄(ADR-0039):AllowsAsync → Result<bool>
Permissions.IPermissionGrantSource存储之外的额外授予(ADR-0045):GetGrantsAsync → Result<IReadOnlyCollection<string>>
SecurityInvocationExtensionsSetCredential / GetCredential、RequireAuthorization / GetAuthorizationRequirement
SecurityOptions选项,见上表
Conditions.*条件引擎(ADR-0046):ConditionSet / ConditionGroup / ConditionClause、ConditionOperator、ConditionValueKind、ConditionAttributeDescriptor、IConditionAttributeRegistry、IConditionCompiler、IConditionEvaluator、IConditionAttributeSource、DictionaryConditionAttributeSource、MemoizingConditionAttributeSource、ConditionOperatorRules、AddConditionAttributes
Network.IIpRegionResolver / IpRegionIP 归属地(ADR-0047):Resolve(IPAddress) → Maybe<IpRegion>
SecurityCapabilities / SecurityErrors / SecurityMessages能力 ID、错误码与消息键常量,消息目录 SecurityMessages.Catalog

使用示例 ​

自定义认证处理器,例如服务间调用用的共享令牌方案:

csharp
using System.Security.Cryptography;
using System.Text;
using XiHan.Framework.Kernel.Security;
using XiHan.Framework.Primitives;
using XiHan.Framework.Security.Abstractions;
using XiHan.Framework.Security.Abstractions.Authentication;

public sealed class ServiceTokenHandler : IAuthenticationHandler
{
    // 开发值;真实令牌从密钥库读取
    private static readonly byte[] _expected = Encoding.UTF8.GetBytes("dev-service-token-0001");

    public string Scheme => "Service";

    public ValueTask<Result<Principal>> AuthenticateAsync(AuthenticationCredential credential, InvocationContext context, CancellationToken cancellationToken)
    {
        var presented = Encoding.UTF8.GetBytes(credential.Value);
        // 常量时间比较,耗时不随匹配位置泄露信息
        var valid = presented.Length == _expected.Length && CryptographicOperations.FixedTimeEquals(presented, _expected);
        return ValueTask.FromResult(valid
            ? Result.Success(new Principal("svc-billing", Scheme) { Claims = [new("role", "service")] })
            : Result.Failure<Principal>(Error.Denied(SecurityErrors.InvalidCredential, SecurityMessages.InvalidToken)));
    }
}

在特性里登记 context.Services.AddSingleton<IAuthenticationHandler, ServiceTokenHandler>()。认证中间件由认证 Provider 加进管道;方案名不与已有处理器重名时,Authorization: Service <token> 就交给它。

在 Authorize 阶段之前的中间件里设置授权要求,例如让没有写授权要求的操作至少要求登录:

csharp
using XiHan.Framework.Kernel.Operations;
using XiHan.Framework.Kernel.Pipeline;
using XiHan.Framework.Primitives;
using XiHan.Framework.Security.Abstractions;
using XiHan.Framework.Security.Abstractions.Authorization;

public sealed class AuthenticatedByDefaultMiddleware : IPipelineMiddleware
{
    public ValueTask InvokeAsync(InvocationContext context, PipelineStep nextStep)
    {
        // 端点没挂要求、契约也没写权限或策略时才补;显式要求优先于契约推导
        if (!context.GetAuthorizationRequirement().HasValue
            && context.GetOperation().TryGetValue(out var operation)
            && operation.Contract is { Permission: null, Policy: null })
        {
            context.RequireAuthorization(AuthorizationRequirement.Authenticated);
        }

        return nextStep(context);
    }
}

在特性里加到 Authorize 之前的阶段:context.AddMiddleware<AuthenticatedByDefaultMiddleware>(PipelineStage.Tenant)。

收窄过滤器、授予来源、自定义策略与条件引擎的示例见授权。

行为与限制 ​

  • 认证:没有凭据保持匿名;方案找不到返回 SECURITY_VALIDATION_001;认证成功保留已有租户;RequireAuthentication 在认证之后检查。
  • 策略查找:注册的策略按名区分大小写匹配;permission:<code> 只为 PermissionRegistry 里登记过的权限码创建;找不到返回 SECURITY_NOTFOUND_001,宁可拒绝不可放行。操作契约的权限码在装配时由内核核对(KERNEL_VALIDATION_005),契约的 Policy 在授权能力启动时经 PolicyCatalog.EnforceOn 核对、端点元数据在 UseXiHan() 建请求管道时核对(SECURITY_VALIDATION_004,启动失败,ADR-0080),这条路径只剩启动时看不到的要求(RequireAuthorization 显式写入的、启动之后才加进来的端点)。
  • PolicyCatalog.EnforceOn(OperationRegistry):核对已登记的全部操作契约的 Policy 都在目录里(permission:<code> 要求权限码已登记),不在即抛出 SECURITY_VALIDATION_004(FrameworkException,一次列出全部);同时经 OperationRegistry.Enforce 挂到登记表上,此后登记的操作(插件握手之后)引用了不存在的策略,登记时返回同一错误码(消息键 security.policy.operation_unregistered,参数 id、owner、policy),整组不登记。security.authorization@1 的 Provider 被选中时在 Configure 里经 FeatureContext.AddStartupCheck 把它登记为启动核对(容器建好之后、任何特性启动之前执行,不论失败策略,抛出即终止启动,ADR-0085);Security.InMemory 与 Security.Database 被选为授权能力时这样登记。
  • 权限判定:匿名主体不持有任何权限,不访问存储;授予按序数精确匹配或 *;顺序是用户授予 → 角色授予 → 授予来源 → 过滤器,命中即止。存储、来源或过滤器返回的失败原样返回(不当作不持有,ADR-0071),抛出的异常照常传播。替换 IPermissionChecker 的实现须同样并上全部来源并施加全部过滤器。
  • PermissionChecker 的构造函数接收全部登记的 IPermissionFilter 与 IPermissionGrantSource(可为空集合),集合里出现 null 抛出 ArgumentException。
  • 失败到 HTTP 的映射(ADR-0042、ADR-0071):SECURITY_DENIED_001 与 SECURITY_DENIED_003 是 401 并带 WWW-Authenticate(Bearer 被拒时为 Bearer error="invalid_token"),其余 DENIED 是 403,SECURITY_UNAVAILABLE_001 是 503,任何一环读不到都不能落到 403;认证处理器内部的基础设施失败抛异常。
  • 收窄过滤器与授予来源:过滤器收到的是具体权限码,持 * 也会被收窄;匿名与未命中授予不调用过滤器;收窄拒绝时权限策略返回 SECURITY_DENIED_004。授予来源在用户与角色的存储授予都没命中时按登记顺序逐个查询,任一命中即算授予命中,之后照常经全部过滤器;匿名不查询。两者都按单例登记(如 services.AddSingleton<IPermissionFilter, EditionPermissionFilter>()),用于套餐白名单、显式拒绝、委托到期、角色继承、限时委托。
  • 条件引擎(ADR-0046):ConditionSet / ConditionGroup / ConditionClause,组内且、组间或;十五个运算符与七种值种类(整数值是持久化契约),ConditionOperatorRules 给出搭配规则;属性按精确名、别名、最长前缀的族解析;编译错误是 SECURITY_VALIDATION_002(消息键说明原因,出错子句保留);求值结果为命中、不命中、无法判定或待定,说明模式逐组逐子句;属性来源 IConditionAttributeSource 由应用提供,自带 DictionaryConditionAttributeSource(试算)与 MemoizingConditionAttributeSource;AddConditionAttributes(...) 登记属性并确保三项单例服务。
  • IIpRegionResolver.Resolve(IPAddress) 对回环、私有网段与查不到返回无值;不装 Provider 时容器里没有本服务,调用方把归属地当作取不到。
  • 条件引擎的登记表在首次解析时建好,重名与别名冲突即抛出;MemoizingConditionAttributeSource 按单次调用使用,不是线程安全的。
错误码含义
SECURITY_DENIED_001未认证
SECURITY_DENIED_002策略拒绝
SECURITY_DENIED_003凭据无效、过期或签名不符
SECURITY_DENIED_004缺少权限
SECURITY_VALIDATION_001不支持的认证方案
SECURITY_VALIDATION_002条件无效(属性未登记、运算符与值种类不搭配、条件值解析不了)
SECURITY_VALIDATION_003条件属性的值不是登记的值种类
SECURITY_VALIDATION_004按名引用的策略不存在:操作契约的 Policy(授权能力的启动核对里,不论失败策略;之后登记的在登记时)、端点元数据(UseXiHan() 建请求管道时)引用了未注册的策略或未登记的权限码;启动失败
SECURITY_NOTFOUND_001调用时才看到的要求(显式写入的、启动之后加进来的端点)引用的策略不存在,或其中 permission:<code> 的权限码未登记
SECURITY_NOTFOUND_002要撤销的授予不存在
SECURITY_NOTFOUND_003取不到条件属性的值
SECURITY_UNAVAILABLE_001身份或授予存储不可达:授予存储、授予来源或收窄过滤器读不到数据(HTTP 503,不当作不持有)
SECURITY_UNAVAILABLE_002身份或授予存储容量已满
SECURITY_UNAVAILABLE_003签发者的发现文档或 JWKS 不可达且没有缓存
SECURITY_UNAVAILABLE_004条件属性暂不可得

消息键(security.*)在本包的 Localization/<区域>.json 里有 10 种区域的文案,Provider 装配时注册 SecurityMessages.Catalog。

相关 ​

Released under The MIT License