Spiga

ABP成长系列5:认证授权

2024-05-11 20:44:04

ABP框架并不重新发明轮子去实现认证,而是完美集成并简化了上述ASP.NET Core的认证流程。

  • 模块化封装:当你使用ABP的启动模板时,认证的配置通常已经在模块中预先配置好,你只需要在appsettings.json中提供必要的参数(如JWT的签发者、密钥等)。
  • 依赖注入集成:ABP通过ICurrentUser提供了对当前登录用户ID (UserId) 和租户ID (TenantId) 的便捷访问。这个接口的背后实现正是基于HttpContext.User及其声明(Claims)。
  • 多租户支持:ABP的认证系统天然支持多租户,可以正确处理不同租户下的用户登录和会话管理。

ABP的主要价值在于整合与简化,它让你在使用强大的ASP.NET Core认证机制的同时,享受到ABP模块化和约定优先开发模式带来的便捷。

要学习好ABP的认证授权模块,我们来看一下ASP.NET Core的认证授权。

一、ASP.NET Core认证授权

1. 基本流程

  1. UseAuthentication + AddAuthentication
  2. UseAuthorization + AddAuthorization(.net源码内置)
  3. [Authorize] [AllowAnonymous]
  4. 未登陆—访问—登陆—访问—退出

2. 理解认证

认证就是鉴别是张三----解读请求携带的用户信息

  1. 信息位置:怎么传递的凭证?Cookie,JWT,Other
  2. 信息格式:凭证是什么格式,加密问题
  3. 信息有效性:token过期了、签名不对
  4. 认证后信息保存:保存到context.User里面
  5. 特殊情况处理:没登陆、没权限、没xxx,这些就是认证需要知道的,需要干的活儿

登陆是怎么配合的?HttpContext.SignInAsync完成用户信息的写入(如:Cookie)

3. 理解授权

授权:就是检测下用户到底有没有权限访问某个方法

认证和授权的关系?

  1. 没有直接关系,认证获取用户信息保存到context.User,授权去使用
  2. 授权的时候,会用Scheme去认证一下,还有异常处理

检测凭据:

  1. 标记特性[Authorize] [AllowAnonymous]—就做授权检测
  2. ASP.NET Core提供了灵活且强大的授权策略模型,主要包括:
    • 基于角色的授权 ([Authorize(Roles = "Admin")])
    • 基于声明的授权 ([Authorize(Policy = "RequireEmail")])
    • 基于策略的授权:这是最强大和灵活的方式,允许你定义复杂的授权需求(Requirements)和处理程序(Handlers)
    • 除此之外,还提供了AuthenticationSchemes:认证方案,为用户信息支持不同的来源
builder.Services.AddAuthorization(options =>
{
    //自定义policy,完成Role的功能
    options.AddPolicy("AdminPolicy", policyBuilder => policyBuilder.RequireRole("Admin"));

    options.AddPolicy("UserPolicy",  policyBuilder => policyBuilder.RequireRole("User"));

    options.AddPolicy("ComplexPolicy", policyBuilder => policyBuilder.RequireRole("Admin").RequireUserName("User").RequireClaim(ClaimTypes.Email)
        //加个断言---一切都可以要求
        .RequireAssertion(context => context.User.HasClaim(c => c.Type == ClaimTypes.Role)
            && context.User.Claims.First(c => c.Type.Equals(ClaimTypes.Role)).Value == "Admin"
            && context.User.HasClaim(c => c.Type == ClaimTypes.Name)
            && context.User.Claims.First(c => c.Type.Equals(ClaimTypes.Name)).Value == "User"
            && context.User.HasClaim(c => c.Type == ClaimTypes.Email)
            && context.User.Claims.First(c => c.Type.Equals(ClaimTypes.Email)).Value.EndsWith("@qq.com")
        )
    );
)};

上面的示例断言都是 && 条件。下面示例提供“或“的支持。

// 提供一个支持2种邮箱支持的自定义策略,继承IAuthorizationRequirement接口
public class DoubleEmailRequirement: IAuthorizationRequirement {}

// qq邮箱的支持
public class QQMailHandler : AuthorizationHandler<DoubleEmailRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DoubleEmailRequirement requirement)
    {
        Console.WriteLine($"**************This is QQMailHandler HandleRequirementAsync");
        if (context.User != null && context.User.HasClaim(c => c.Type == ClaimTypes.Email))
        {
            // 如果需要动态条件,这里可以查询数据库
            var emailCliamList = context.User.FindAll(c => c.Type == ClaimTypes.Email);//支持多Scheme
            if (emailCliamList.Any(c => c.Value.EndsWith("@qq.com", StringComparison.OrdinalIgnoreCase)))
            {
                context.Succeed(requirement);
            }
            else
            {
                //context.Fail();//不设置失败 交给其他处理
            }
        }
        return Task.CompletedTask;
    }
}

// ali邮箱的支持
public class AliMailHandler : AuthorizationHandler<DoubleEmailRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DoubleEmailRequirement requirement)
    {
        Console.WriteLine($"**************This is AliMailHandler HandleRequirementAsync");
        if (context.User != null && context.User.HasClaim(c => c.Type == ClaimTypes.Email))
        {
            // 如果需要动态条件,这里可以查询数据库
            var emailCliamList = context.User.FindAll(c => c.Type == ClaimTypes.Email);//支持多Scheme
            if (emailCliamList.Any(c => c.Value.EndsWith("@ali.com", StringComparison.OrdinalIgnoreCase)))
            {
                context.Succeed(requirement);
            }
            else
            {
                //context.Fail();//不设置失败 交给其他处理
            }
        }
        return Task.CompletedTask;
    }
}

builder.Services.AddSingleton<IAuthorizationHandler, QQMailHandler>();
builder.Services.AddSingleton<IAuthorizationHandler, AliMailHandler>();

builder.Services.AddAuthorization(options =>
	options.AddPolicy("DoubleEmailPolicy", policyBuilder => policyBuilder.AddRequirements(new DoubleEmailRequirement()));
);

4. 自定义认证与授权

关于如何自定义认证和授权,包括认证授权的源码,可以查看 http://localhost:50415/2023/7/asp-net-core-source-code-reading-5.html

ABP的扩展原理也差不多,建议在学习ABP的扩展前,先弄清楚ASP.NET Core的认证授权源码实现。

二、ABP Authentication

1. Authentication的内容

ASP.NET Core 原生

  • 基于ASP.NET Core Authentication
  • ASP.NET Core Identity
  • Social/External Logins
  • Identity Server 4

ABP扩展

  • Security
  • Identity Module
  • Account Module

2. Security

在ABP vNext框架中,提供了两个与当前用户身份相关的核心接口,它们为应用提供了统一且类型安全的方式来访问当前用户信息。这两个接口的设计体现了ABP框架对安全性和扩展性的重视。

ICurrentPrincipalAccessor:访问原始安全主体

  • 基础层访问:提供对ASP.NET Core原始ClaimsPrincipal对象的访问能力。
  • 解耦HttpContext:抽象了获取当前用户主体的方式,避免直接依赖HttpContext.User,使代码更容易进行单元测试。
  • 多环境支持:不仅适用于Web请求,也可用于后台任务、消息队列处理等非HTTP场景。
  • 源码
public interface ICurrentPrincipalAccessor
{
    ClaimsPrincipal Principal { get; }

    IDisposable Change(ClaimsPrincipal principal);
}

public abstract class CurrentPrincipalAccessorBase : ICurrentPrincipalAccessor
{
    public ClaimsPrincipal Principal => _currentPrincipal.Value ?? GetClaimsPrincipal();

    private readonly AsyncLocal<ClaimsPrincipal> _currentPrincipal = new AsyncLocal<ClaimsPrincipal>();

    protected abstract ClaimsPrincipal GetClaimsPrincipal();

    public virtual IDisposable Change(ClaimsPrincipal principal)
    {
        return SetCurrent(principal);
    }

    private IDisposable SetCurrent(ClaimsPrincipal principal)
    {
        var parent = Principal;					// 临时变量保存当前身份
        _currentPrincipal.Value = principal;    // 切换身份
        return new DisposeAction(() =>
        {
            _currentPrincipal.Value = parent;	// 释放的时候,回到当前身份
        });
    }
}
  • Principal属性使用场景
//1 需要直接操作Claims
var userIdClaim = _principalAccessor.Principal?.FindFirst(ClaimTypes.NameIdentifier);

//2 与第三方库集成
//某些库可能需要原始的ClaimsPrincipal对象进行授权或审计:
thirdPartyService.Process(_principalAccessor.Principal);

//3 自定义认证逻辑
var principal = _principalAccessor.Principal;
if (principal?.Identity.IsAuthenticated == true)
{
    // 自定义验证逻辑
}
  • Change方法使用场景
//非Web环境模拟用户
using (_currentPrincipalAccessor.Change(new ClaimsPrincipal(
        new ClaimsIdentity(new[] { new Claim(AbpClaimTypes.UserId, "12345") }))))
{
    // 在此作用域内,_currentUser.Id == "12345"
    await _dataProcessor.ProcessAsync();
}
// 作用域外,回到原始用户身份

ICurrentUser:类型安全的用户信息访问

  • 业务层友好:提供类型安全的属性(如Id, UserName)和方法,避免直接处理字符串类型的Claim。
  • 租户集成:自动处理租户上下文,提供TenantId等租户感知属性。
  • 空值安全:所有属性均可为null,避免在未认证时抛出异常。

注意:Id是Guid类型,如果使用mysql持久化,要自行处理guid类型

Security类关系图

协助示例

// 自定义ICurrentUser实现时依赖ICurrentPrincipalAccessor
public class AbpCurrentUser : ICurrentUser, ITransientDependency
{
    private readonly ICurrentPrincipalAccessor _principalAccessor;

    public AbpCurrentUser(ICurrentPrincipalAccessor principalAccessor)
    {
        _principalAccessor = principalAccessor;
    }

    public Guid? Id
    {
        get
        {
            var userIdClaim = _principalAccessor.Principal?.FindFirst(AbpClaimTypes.UserId);
            if (string.IsNullOrEmpty(userIdClaim?.Value)) return null;
            return Guid.Parse(userIdClaim.Value);
        }
    }
    // 其他属性实现类似...
}

3. Identity Module

ABP的Identity模块建立在ASP.NET Core Identity的基础之上,但进行了模块化、DDD(领域驱动设计)化和增强,使其更适用于分布式和模块化系统开发。它的核心作用包括:

  • 用户管理(User Management):提供用户的创建、删除、查找、更新等全套CRUD操作,并支持邮箱确认、手机号绑定等功能。
  • 角色管理(Role Management):允许你定义不同的角色(如Admin、User、Manager),并为角色分配权限
  • 权限管理(Permission Management):这是ABP在ASP.NET Core Identity基础上强化的核心。它允许你定义细粒度的权限(如Identity.User.Create),并将这些权限授予角色或直接授予用户。这套权限系统会无缝集成到ABP的授权框架中(如[Authorize]特性)。
  • 身份验证(Authentication):支持基于Cookie和JWT Bearer Token的认证方式,方便Web应用和API接口。
  • 安全特性(Security Features):内置了密码哈希、密码复杂度策略、账户锁定(防止暴力破解)、双因素认证(2FA)等安全机制。
  • 外部登录集成(External Login):可以快速集成第三方登录提供商,如Google、Facebook、Microsoft、Twitter等

当你使用ABP框架启动一个新项目时,集成Identity模块通常是为了快速构建以下功能:

  • 应用程序的注册与登录系统:你不再需要从零开始编写用户注册、登录、退出、忘记密码的流程和界面。Identity模块提供了默认的实现,通常只需稍作定制即可使用。
  • 用户和角色的后台管理:它会提供一个现成的UI界面(如果你使用了ABP的UI模块)和API接口,让你能够管理你应用的用户和角色。这对于后台管理系统是至关重要的。
  • 为你的应用服务提供授权基础:你定义的业务权限(如Inventory.Product.Delete)可以依赖于Identity模块管理的用户和角色体系。你可以在应用服务的方法上使用[Authorize]特性来控制访问。
  • 快速实现多租户(Multi-tenancy)下的用户隔离:ABP框架天然支持多租户,Identity模块也很好地集成了这一特性,可以自动隔离不同租户下的用户和数据。

Identity类关系图

核心服务接口

集成Identity模块后,以下一些核心服务会被注入到依赖注入系统中,可以在你的应用代码中直接使用它们:

  • IdentityUserManager:用于管理用户(创建、删除、查找、修改密码等)。
  • IdentityRoleManager:用于管理角色。
  • IIdentityUserRepository/IIdentityRoleRepository:用于对用户和角色进行更底层的数据库操作。

4. Account Module

Account 模块建立在 ASP.NET Core Identity 和 ABP Identity 模块的基础之上,但它的关注点不同:

  • Identity 模块:更侧重于后端管理,提供用户、角色、权限的 CRUD API 和管理界面(通常供管理员使用)。它管理的是“用户是什么”。
  • Account 模块:更侧重于前端交互,提供标准的登录页、注册页、忘记密码页等,让最终用户能够管理自己的账户。它解决的是“用户如何证明自己是谁并进入系统”。

虽然 Account 模块提供了默认实现,但 ABP 也允许你进行高度自定义:

  • 重写页面 UI:可以通过创建同名 .cshtml 文件到你的项目中,来重写默认的登录、注册等页面的视图,从而定制UI和布局。
  • 自定义逻辑:可以实现 IAccountAppService 或继承自其默认实现,来重写登录、注册等过程中的业务逻辑,例如添加自定义的验证规则。
  • 集成自定义认证:如果有特殊的认证需求(如短信验证码登录),可以扩展 Account 模块的流程。

Account 模块与 Identity 模块的关系

特性 Account Module Identity Module
核心焦点 用户认证和前端交互 用户身份和后端管理
主要功能 登录、注册、忘记密码、社交登录 用户/角色的CRUD、权限分配
主要使用者 最终用户 系统管理员
提供的界面 登录页、注册页等公共页面 用户管理页、角色管理页等后台管理页面
关系 依赖于 Identity Module 提供的用户存储和基础功能 被 Account Module 所依赖,为其提供用户数据支撑

三、ABP Authorization

1. ABP的授权抽象与增强

ABP在ASP.NET Core授权的基础上,引入了权限(Permission)的概念,并提供了一整套用于定义和管理权限的基础设施,这使得授权更加系统化和可维护。

以下是ABP授权核心概念与ASP.NET Core的对比:

概念 ASP.NET Core ABP Framework ABP 的优势
授权单元 角色(Roles)、声明(Claims)、策略(Policies) 权限(Permissions) 更细粒度、更面向业务逻辑的授权单元
定义方式 在启动类中通过代码配置 在AuthorizationProvider中通过代码定义(模块化) 定义更集中、更模块化,与功能模块紧密结合
检查方式 [Authorize]特性、IAuthorizationService [AbpAuthorize]特性、IPermissionChecker 提供统一的同步/异步检查接口,与ABP框架(如应用服务)无缝集成
管理界面 无内置,需自行开发 在Module Zero中提供完整的权限管理UI 为系统管理员提供可视化权限管理能力

Authorization类关系图

2. Permission System

权限在ABP中是模块化的。每个模块都可以通过创建AuthorizationProvider的派生类来定义自己的权限

// 在应用层(通常是.Application项目)中定义权限提供者
public class MyAppAuthorizationProvider : AuthorizationProvider
{
    public override void SetPermissions(IPermissionDefinitionContext context)
    {
        // 创建一个权限组(在UI中会分组显示)
        var administration = context.CreatePermission("Administration");
        
        // 在组内创建管理子权限
        var userManagement = administration.CreateChildPermission("Administration.UserManagement");
        
        // 为用户管理创建具体的操作权限
        userManagement.CreateChildPermission("Administration.UserManagement.CreateUser");
        userManagement.CreateChildPermission("Administration.UserManagement.DeleteUser");
        userManagement.CreateChildPermission("Administration.UserManagement.EditUser");
        
        // 可以继续定义其他权限...
        var taskManagement = administration.CreateChildPermission("Administration.TaskManagement");
        taskManagement.CreateChildPermission("Administration.TaskManagement.AssignTask");
    }
}

// 在模块的PreInitialize方法中注册权限提供者
public override void PreInitialize()
{
    Configuration.Authorization.Providers.Add<MyAppAuthorizationProvider>();
}

核心1:AbpPermissionPolicyProvider

ABP的权限系统AbpPermissionPolicyProvider实现与ASP.NET Core的策略系统之间建立桥梁,让ABP的权限能够被ASP.NET Core的[Authorize(Policy = "...")]特性使用。

// 创建一个策略提供者,将ABP的权限转换为策略
public class AbpAuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider, IAbpAuthorizationPolicyProvider, ITransientDependency
{
    private readonly AuthorizationOptions _options;
    private readonly IPermissionDefinitionManager _permissionDefinitionManager;

    public AbpAuthorizationPolicyProvider(
        IOptions<AuthorizationOptions> options,
        IPermissionDefinitionManager permissionDefinitionManager)
        : base(options)
    {
        _permissionDefinitionManager = permissionDefinitionManager;
        _options = options.Value;
    }

    public override async Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
    {
        var policy = await base.GetPolicyAsync(policyName);
        if (policy != null)
        {
            return policy;
        }

        var permission = _permissionDefinitionManager.GetOrNull(policyName);
        if (permission != null)
        {
            //TODO: Optimize & Cache!
            var policyBuilder = new AuthorizationPolicyBuilder(Array.Empty<string>());
            policyBuilder.Requirements.Add(new PermissionRequirement(policyName));
            return policyBuilder.Build();
        }

        return null;
    }

    public Task<List<string>> GetPoliciesNamesAsync()
    {
        return Task.FromResult(
            _options.GetPoliciesNames()
                .Union(
                    _permissionDefinitionManager
                        .GetPermissions()
                        .Select(p => p.Name)
                )
                .ToList()
        );
    }
}

核心2:PermissionAuthorizationHandler

// 创建对应的需求处理程序
public class PermissionRequirementHandler : AuthorizationHandler<PermissionRequirement>
{
    private readonly IPermissionChecker _permissionChecker;

    public PermissionRequirementHandler(IPermissionChecker permissionChecker)
    {
        _permissionChecker = permissionChecker;
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
    {
        if (await _permissionChecker.IsGrantedAsync(context.User, requirement.PermissionName))
        {
            context.Succeed(requirement);
        }
    }
}

public class PermissionsRequirementHandler : AuthorizationHandler<PermissionsRequirement>
{
    private readonly IPermissionChecker _permissionChecker;

    public PermissionsRequirementHandler(IPermissionChecker permissionChecker)
    {
        _permissionChecker = permissionChecker;
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionsRequirement requirement)
    {
        var multiplePermissionGrantResult = await _permissionChecker.IsGrantedAsync(context.User, requirement.PermissionNames);

        if (requirement.RequiresAll ?
            multiplePermissionGrantResult.AllGranted :
            multiplePermissionGrantResult.Result.Any(x => x.Value == PermissionGrantResult.Granted))
        {
            context.Succeed(requirement);
        }
    }
}

// AbpAuthorizationModule中注册
context.Services.AddSingleton<IAuthorizationHandler, PermissionRequirementHandler>();
context.Services.AddSingleton<IAuthorizationHandler, PermissionsRequirementHandler>();

context.Services.TryAddTransient<DefaultAuthorizationPolicyProvider>();

3. 检查权限(Permission Check)

ABP提供了两种主要方式来检查权限:

声明式(使用特性)—— 最常用、最推荐

使用[AbpAuthorize]特性来装饰应用服务的方法或类。这是ABP授权的首选方式,因为它简洁、清晰且不易出错。

public class UserAppService : ApplicationService, IUserAppService
{
    // 要求用户必须拥有 CreateUser 权限才能调用此方法
    [AbpAuthorize("Administration.UserManagement.CreateUser")]
    public async Task CreateUser(CreateUserDto input)
    {
        // 你的业务逻辑
    }
    
    // 要求用户同时拥有 DeleteUser 和 EditUser 权限
    [AbpAuthorize("Administration.UserManagement.DeleteUser", "Administration.UserManagement.EditUser")]
    public async Task ManageUser(ManageUserDto input)
    {
        // 你的业务逻辑
    }
    
    // 仅要求用户登录即可
    [AbpAuthorize]
    public async Task<UserDto> GetUserProfile()
    {
        // 你的业务逻辑
    }
}

程序式(手动检查)—— 更灵活,用于复杂逻辑

在方法内部通过注入的IPermissionChecker服务进行权限检查。这种方式适用于那些需要根据运行时条件动态决定是否进行权限检查的场景。

public class ComplexUserAppService : ApplicationService
{
    // 已经由基类 ApplicationService 提供了 PermissionChecker 属性
    // 如果需要,也可以通过构造函数注入 IPermissionChecker
    public async Task SomeComplexOperation(SomeInput input)
    {
        // 复杂的业务逻辑可能需要在中间某一步检查权限
        if (input.SomeCondition && 
            !await PermissionChecker.IsGrantedAsync("Administration.UserManagement.SpecialPermission"))
        {
            // 抛出授权异常
            throw new AbpAuthorizationException("You don't have the required permission to perform this action.");
        }
        
        // 或者使用 Authorize 方法,失败直接抛出异常
        PermissionChecker.Authorize("Administration.UserManagement.AnotherPermission");
        
        // 继续你的业务逻辑...
    }
}

核心:IPermissionValueProvider

IPermissionValueProvider 是权限系统的核心扩展点,它定义了如何检查一个权限是否被授予用户。

public interface IPermissionValueProvider
{
   	// 提供器的唯一名称(如 "Role"、"User" 等)
    string Name { get; }

    // TODO: Rename to GetResult? (CheckAsync throws exception by naming convention)
    // 检查指定用户是否被授予单个权限,返回 PermissionGrantResult(Granted、Prohibited 或 Undefined)。
    Task<PermissionGrantResult> CheckAsync(PermissionValueCheckContext context);
	// 优化场景:同时检查多个权限,返回每个权限的结果(避免重复调用提供器)。
    Task<MultiplePermissionGrantResult> CheckAsync(PermissionValuesCheckContext context);
}

ABP 默认提供了三个权限值提供器,默认顺序:Role → User → Client:

  1. RolePermissionValueProvider:名称: R,检查用户是否通过角色获得权限。
  2. UserPermissionValueProvider:名称: U,检查用户是否被直接授予权限(绕过角色)。
  3. ClientPermissionValueProvider:名称: C,检查客户端(如API消费者)是否被授予权限(OAuth 2.0客户端凭证流程)。
// 自定义提供器示例
public class DepartmentPermissionValueProvider : IPermissionValueProvider, ITransientDependency
{
    public const string ProviderName = "D"; // 唯一名称

    public string Name => ProviderName;

    public async Task<PermissionGrantResult> CheckAsync(PermissionValueCheckContext context)
    {
        var departmentId = context.Principal?.FindFirst("DepartmentId")?.Value;
        if (departmentId == null) return PermissionGrantResult.Undefined;

        // 检查部门是否拥有权限(从数据库或其他存储)
        var hasPermission = await _departmentPermissionStore.IsGrantedAsync(
            departmentId, 
            context.Permission.Name
        );
        
        return hasPermission ? PermissionGrantResult.Granted : PermissionGrantResult.Undefined;
    }

    // 实现批量检查方法(略)
}

// 应用
Configure<AbpPermissionOptions>(options =>
{
    options.ValueProviders.Add<DepartmentPermissionValueProvider>(); // 默认添加到末尾
    // 或指定位置
    options.ValueProviders.Insert(0, typeof(DepartmentPermissionValueProvider)); // 最高优先级
});

核心2:IsGrantedAsync(ClaimsPrincipal, string):检查单个权限是否被授予指定的用户

public virtual async Task<bool> IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string name)
{
    Check.NotNull(name, nameof(name));

    //从 PermissionDefinitionManager 中获取权限定义(PermissionDefinition),包含权限的元数据(如是否启用、多租户配置等)。
    var permission = PermissionDefinitionManager.Get(name);

    // 权限是否全局启用
    if (!permission.IsEnabled) return false;

    // 状态检查:通过 StateCheckerManager 检查权限是否在运行时被动态禁用(例如基于功能开关或其他条件)。
    if (!await StateCheckerManager.IsEnabledAsync(permission)) return false;

    // 检查权限是否支持当前租户上下文(Host、Tenant 或 Both)。
    var multiTenancySide = claimsPrincipal?.GetMultiTenancySide() ?? CurrentTenant.GetMultiTenancySide();
    if (!permission.MultiTenancySide.HasFlag(multiTenancySide)) return false;

    var isGranted = false;
    var context = new PermissionValueCheckContext(permission, claimsPrincipal);
    foreach (var provider in PermissionValueProviderManager.ValueProviders)
    {
        // 如果权限指定了提供器且当前提供器不在列表中,则跳过
        if (context.Permission.Providers.Any() && !context.Permission.Providers.Contains(provider.Name)) continue;

        var result = await provider.CheckAsync(context);

        if (result == PermissionGrantResult.Granted)
        {
            isGranted = true; //如果任一提供器返回 Granted,最终结果为 true;
        }
        else if (result == PermissionGrantResult.Prohibited)
        {
            return false; //如果任一提供器返回 Prohibited,直接拒绝(短路返回)。
        }
    }

    return isGranted;
}

核心3:IsGrantedAsync(ClaimsPrincipal, string[]):批量检查多个权限是否被授予指定的用户,返回每个权限的独立授权结果。

public async Task<MultiplePermissionGrantResult> IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string[] names)
{
    Check.NotNull(names, nameof(names));

    var multiTenancySide = claimsPrincipal?.GetMultiTenancySide() ?? CurrentTenant.GetMultiTenancySide();

    // 初始化结果对象 MultiplePermissionGrantResult,其 Result 属性是一个字典,键为权限名,值为 PermissionGrantResult。
    var result = new MultiplePermissionGrantResult();
    if (!names.Any()) return result;

    // 过滤掉未启用、不满足多租户要求或动态禁用的权限,剩余权限存入 permissionDefinitions。
    var permissionDefinitions = new List<PermissionDefinition>();
    foreach (var name in names)
    {
        var permission = PermissionDefinitionManager.Get(name);

        result.Result.Add(name, PermissionGrantResult.Undefined);

        if (permission.IsEnabled && await StateCheckerManager.IsEnabledAsync(permission) && permission.MultiTenancySide.HasFlag(multiTenancySide))
        {
            permissionDefinitions.Add(permission);
        }
    }

    foreach (var provider in PermissionValueProviderManager.ValueProviders)
    {
        var permissions = permissionDefinitions.Where(x => !x.Providers.Any() || x.Providers.Contains(provider.Name)).ToList();

        if (permissions.IsNullOrEmpty()) break;

        var context = new PermissionValuesCheckContext(permissions, claimsPrincipal);

        var multipleResult = await provider.CheckAsync(context);
        // 更新结果并移除已确定的权限
        foreach (var grantResult in multipleResult.Result.Where(grantResult =>
            result.Result.ContainsKey(grantResult.Key) &&
            result.Result[grantResult.Key] == PermissionGrantResult.Undefined &&
            grantResult.Value != PermissionGrantResult.Undefined))
        {
            result.Result[grantResult.Key] = grantResult.Value;
            permissionDefinitions.RemoveAll(x => x.Name == grantResult.Key);
        }

        // 短路优化​:如果所有权限已确定(全部授予或全部禁止),提前退出循环。
        if (result.AllGranted || result.AllProhibited) break;
    }

    return result;
}

自定义权限检查器

public class CustomPermissionChecker : IPermissionChecker, ITransientDependency
{
    public Task<bool> IsGrantedAsync(string permissionName)
    {
        // 你的自定义权限检查逻辑
        // 例如从Redis或App配置中检查
        return Task.FromResult(CheckPermission(permissionName));
    }

    public Task<bool> IsGrantedAsync(UserIdentifier user, string permissionName)
    {
        // 检查指定用户的权限
        return Task.FromResult(CheckPermissionForUser(user, permissionName));
    }
    
    private bool CheckPermission(string permissionName) { ... }
    private bool CheckPermissionForUser(UserIdentifier user, string permissionName) { ... }
}

// 在模块中替换默认实现
public override void PreInitialize()
{
    // 替换IPermissionChecker的实现
    Configuration.ReplaceService<IPermissionChecker, CustomPermissionChecker>(DependencyLifeStyle.Transient);
}

4. PermissionManager

PermissionManager 在 ABP vNext 框架中是一个核心的单例服务,它并不直接用于“扩展”,而是作为权限系统的中枢,负责权限的定义、存储和管理。它协调整个权限系统的生命周期,确保所有模块定义的权限能够被正确收集、组织,并提供给授权系统使用。

PermissionManager 实现了 IPermissionManager 接口,并继承自 PermissionDefinitionContextBase 类

PermissionManager 的工作流程

理解 PermissionManager 的工作流程,能更好地把握ABP权限系统的全貌:

  1. 启动时收集:在应用启动阶段,PermissionManager 的 Initialize 方法被调用。
  2. 调用提供器:该方法会遍历所有注册的 AuthorizationProvider(例如,你项目中定义的 XXXAuthorizationProvider)。
  3. 构建字典:每个 AuthorizationProvider 通过其 SetPermissions 方法,使用传入的 context(其背后通常是 PermissionManager 本身)来创建(CreatePermission)和配置权限定义。
  4. 存储完毕:所有权限定义被收集并存储在 PermissionManager 内部的 PermissionDictionary 中。
  5. 提供服务:此后,权限检查系统(如 IPermissionChecker)、授权拦截器(AuthorizationInterceptor)以及其他需要权限信息的服务,都可以通过 PermissionManager 来查询和获取权限定义。