ABP Framework-Permission&Authorization源码解析

目录

https://abp.io/docs/6.0/Authorization

前提了解

https://learn.microsoft.com/zh-cn/aspnet/core/security/authorization/policies?view=aspnetcore-8.0

ABP基于Asp.Net Core的授权检查与权限声明体系,使用[AuthorizeAttribute],而对于Asp.Net Core的权限声明简要描述下:

  1. Controller/Action上加AuthorizeAttribute,使用Role或Policy来限制方法的访问。

  2. 请求到达时,Authorize判断前提默认为鉴权通过的标记了IsAuthenticated。其次会按照Role或Policy,从当前请求的HttpContext中或者查询数据库/服务中,拿到有关信息,如果符合Role或Policy则授权检查通过。

  3. 请求方法,执行逻辑,返回结果。

一般场景很少直接使用Role,并且此文基于ABP的实践,采用ABP中的权限定义来作为Policy,具体便是,方法上标记Policy=PermissionDefinition.XXXName。在授权检查时,查询当前人的角色或自身具备了该权限,则授权检查通过。

Version

6.0.3

Package

Volo.Abp.Authorization
Volo.Abp.Authorization.Abstractions


//独立模块
Volo.Abp.PermissionManagement.*

权限定义

权限定义树

在各垂直业务的独立模块中,一般会定义各自的权限定义树,比如User/Tenant等,也比如订单模块,地址模块等,都会在模块内构建好自身的权限定义树。

public static class WorkSpacePermissions
{
    public const string GroupName = "WorkSpaceManagement";


    public static class Projects
    {
        public const string Default = GroupName + ".Projects";
        public const string Create = Default + ".Create";
        public const string Update = Default + ".Update";
        public const string Delete = Default + ".Delete";
        public const string ManageTypes = Default + ".ManageTypes";


        public static class Members
        {
            public const string Default = Projects.Default + ".Members";
            public const string Create = Default + ".Create";
            public const string Update = Default + ".Update";
            public const string Delete = Default + ".Delete";
            public const string ManageRoles = Default + ".ManageRoles";
            public const string ManageInvites = Default + ".ManageInvites";
        }
    }
}


public class WorkSpacePermissionDefinitionProvider : PermissionDefinitionProvider
{
  public override void Define(IPermissionDefinitionContext context)
  {
      var projectsPermission = workSpaceGroup.AddPermission(WorkSpacePermissions.Projects.Default);
      projectsPermission.AddChild(WorkSpacePermissions.Projects.Create);
      projectsPermission.AddChild(WorkSpacePermissions.Projects.Update);
      projectsPermission.AddChild(WorkSpacePermissions.Projects.Delete);
      projectsPermission.AddChild(WorkSpacePermissions.Projects.ManageTypes);
  
      var projectMembersPermission = projectsPermission.AddChild(WorkSpacePermissions.Projects.Members.Default);
      projectMembersPermission.AddChild(WorkSpacePermissions.Projects.Members.Create);
      projectMembersPermission.AddChild(WorkSpacePermissions.Projects.Members.Update);
      projectMembersPermission.AddChild(WorkSpacePermissions.Projects.Members.Delete);
      projectMembersPermission.AddChild(WorkSpacePermissions.Projects.Members.ManageRoles);
      projectMembersPermission.AddChild(WorkSpacePermissions.Projects.Members.ManageInvites);
  }
}

可以构建多级权限(顶级Group不参与权限)只用来权限分组。在ABP6.0版本,该部分权限定义不会持久化到数据库中,此处仅以6.0版本为主(8.0会持久化)。 204122669_040de5be-d22a-4994-9098-bf1c3d98c8e9 IPermissionDefinitionProvider中定义了如下三个接口,但使用频繁的还是Define

public interface IPermissionDefinitionProvider
{
    void PreDefine(IPermissionDefinitionContext context);
    void Define(IPermissionDefinitionContext context);
    void PostDefine(IPermissionDefinitionContext context);
}

权限定义汇总

PermissionDefinitionProvider会在启动时在Module初始化时完成扫描注册到AbpPermissionOptions的PermissionDefinitionProviders属性中。

204123897_1b1f5ef5-9c3d-4757-8af9-9de92343dc37 在权限检查应用时,不直接从AbpPermissionOptions取,而是抽象一层IPermissionDefinitionManager,其中封装了取权限和取Group的方法。简要类图如下。

204125536_89f4751c-d66b-4fc2-83f5-250ad38111b7

权限声明

对于Controller/Action上,和Asp.Net Core的授权方式相同,使用AuthorizeAttribute特性,加上权限定义,该定义过程不涉及ABP源码,简单示例如下。

[HttpPost]
[Authorize(WorkSpacePermissions.Projects.Create)]
public async Task<ProjectDto> CreateAsync(CreateProjectDto input)
{
    return await _projectAppService.CreateAsync(input);
}

当在ApplicationService中使用时,同样如此,ABP没有额外封一个AuthorizeAttribute。

[Authorize(WorkSpacePermissions.Projects)]
public class ProjectAppService : ApplicationService, IProjectAppService
{
    [Authorize(WorkSpacePermissions.Projects.Create)]
    public Task<ProjectDto> CreateAsync(CreateProjectDto input)
    {
        ...
    }
}

还有一种场景是在方法内执行权限声明

public class ProjectAppService : ApplicationService, IProjectAppService
{
    public Task<ProjectDto> CreateAsync(CreateProjectDto input)
    {
        var result = await AuthorizationService
              .AuthorizeAsync(WorkSpacePermissions.Projects.Create);
        if (result.Succeeded == false)
        {
            //throw exception
            throw new AbpAuthorizationException("...");
        }
    }
}

三种方式有所不同,处理的时机,出发点,方式不同,最终使用的Handler方式相同。

  • 对于Controlller/Action来讲,是由Asp.Net Core的授权中间件来处理,调用Handler经过一系列判断执行授权检查。

  • 而对于在ApplicationService或Method上标记,则借助拦截器完成,经过拦截,最终使用上Handler,具体见后续小节。

  • 对于方法内的授权检查,实际上还是利用Asp.Net Core提供的AuthorizationService,和中间件中内部用的方式相同,最终走到Handler中处理。

授权检查

服务注册

当请求到达,进入Controller/Action时,授权检查开始工作,此处先提及AbpAuthorizationModule.cs中服务注册内容,因为请求时依赖该部分注册的服务。

204126514_302bf032-cd04-4cff-ad15-b3ff672468cb

  • 第一部分为拦截器注册,是ApplicationService/Method上如果标记了AuthorizeAttribute的处理部分。

  • 第二部分中,注册了AuthorizationCore,这是授权检查必不可少的服务,其次加入了Abp封装的PermissionRequirementHandler,具体的授权检查基于该Handler。还有一个AuthorizationPolicyProvider,这种方式是Asp.Net Core提供的动态Policy转换,使用多,扩展方便。Abp封装了一个PolicyProvider便于将Policy转换为PermissionRequirement,再经PermissionRequirementHandler处理。

  • 第三部分中,是具体权限值的提供,是否拥有权限的数据来源,内部一般是读取数据库拿到值。

Policy转换

如上一节提到了AuthorizationPolicyProvider,其中将Policy转换成Requirement,在ABP封装的类中,此处查看其简化后核心源码。

public class AbpAuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider, IAbpAuthorizationPolicyProvider, ITransientDependency
{
    public override async Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
    {
        // PolicyName转换到AuthorizationPolicy,前置判断
        var policy = await base.GetPolicyAsync(policyName);
        if (policy != null)
        {
            return policy;
        }


        // 检查该权限是否在权限定义树中
        var permission = _permissionDefinitionManager.GetOrNull(policyName);
        if (permission != null)
        {
            // 转换Policy到ABP的PermissionRequirement
            var policyBuilder = new AuthorizationPolicyBuilder(Array.Empty<string>());
            policyBuilder.Requirements.Add(new PermissionRequirement(policyName));
            return policyBuilder.Build();
        }


        return null;
    }
}

AbpAuthorizationPolicyProvider相关类图与依赖如下。 204127911_77a92347-228f-41fd-85aa-38a96139a4ae 该类完成了对Policy到Requirement的动态转换,接下来便是Handler对Requirement的授权检查。

授权检查

ABP封装了两个RequirementHandler(PermissionRequirementHandler,PermissionsRequirementHandler),其中一个是对于多个权限的处理,此处不考虑,只关注PermissionRequirementHandler,其中核心是IPermissionCheck。

204128946_fc062d18-67d0-4643-91a8-feef7cda5f6f 该部分类图如下

204130510_c679ddd6-c4ce-44b5-b43a-5bacca6ed735其中RemotePermissionChecker用于Mvc独立项目远程授权检查使用,此处不考虑,而对于AlwaysAllowPermissionChecker,则更多在测试项目中使用,以隔离权限检查。如下简化实际代码关注核心部分。

public class PermissionChecker : IPermissionChecker, ITransientDependency
{
    public virtual async Task<bool> IsGrantedAsync(
        ClaimsPrincipal claimsPrincipal,
        string name)
    {
        // 权限是否存在于权限定义树中
        var permission = PermissionDefinitionManager.Get(name);
        if (!permission.IsEnabled)
        {
            return false;
        }


        var isGranted = false;
        var context = new PermissionValueCheckContext(permission, claimsPrincipal);


        // 遍历初始化时注册好的权限值提供来源
        foreach (var provider in PermissionValueProviderManager.ValueProviders)
        {
            // 进入到提供者内执行是否被赋权判断
            var result = await provider.CheckAsync(context);
            if (result == PermissionGrantResult.Granted)
            {
                isGranted = true;
            }
            else if (result == PermissionGrantResult.Prohibited)
            {
                return false;
            }
        }


        return isGranted;
    }
}

参照同样的方式,如果有场景需要支持其他权限检查,可以扩展。

权限值提供者

PermissionCheck最终进入到PermissionValueProvider,大多数是查询数据库执行匹配,当然也可以扩展想要的场景。但PermissionCheck不直接和PermissionValueProvider交互,而是由PermissionValueProviderManager来统筹管理众多的PermissionValueProvider。该部分类图简要如下。

204131720_c7c824a4-b041-4914-be55-b8220d01bf67 在具体的PermissionValueProvider中实际上调用IPermissionStore来得到是否具有分配的权限,默认情况下其实现是PermissionStore从数据库表中读取对应权限。如下简要查看下UserPermissionValueProvider。

public class UserPermissionValueProvider : PermissionValueProvider
{
    public const string ProviderName = "U";
    public override string Name => ProviderName;


    public override async Task<PermissionGrantResult> CheckAsync(PermissionValueCheckContext context)
    {
        // 从请求信息中获取和分配权限有关的标识
        var userId = context.Principal?.FindFirst(AbpClaimTypes.UserId)?.Value;
        if (userId == null)
        {
            return PermissionGrantResult.Undefined;
        }
        
        // 根据U,UserId及权限名查询授权表中是否存在记录
        return await PermissionStore.IsGrantedAsync(context.Permission.Name, Name, userId)
            ? PermissionGrantResult.Granted
            : PermissionGrantResult.Undefined;
    }
}

AOP拦截器

该部分内容是在ApplicationService/Method上标记AuthorizeAttribute,在代码中当请求到该部分接口时,会执行授权检查,其最终实现还是调用的前几节内容,只是触发时机,位置不同。

在服务注册一节中AbpAuthorizationModule注册了AuthorizationInterceptorRegistrar,其内部判断了需要拦截的类型,当满足条件后,则加上拦截器AuthorizationInterceptor。

204132816_8a7e5547-e8cf-4827-8ecf-d777d4c5add8 在AuthorizationInterceptor拦截器内部,调用IMethodInvocationAuthorizationService,其内部则是调用的IAbpAuthorizationPolicyProvider和IAbpAuthorizationService,便是回到了授权检查中间件中所用到的服务了。

public class MethodInvocationAuthorizationService : IMethodInvocationAuthorizationService, ITransientDependency
{
    private readonly IAbpAuthorizationPolicyProvider _abpAuthorizationPolicyProvider;
    private readonly IAbpAuthorizationService _abpAuthorizationService;


    public MethodInvocationAuthorizationService(
        IAbpAuthorizationPolicyProvider abpAuthorizationPolicyProvider,
        IAbpAuthorizationService abpAuthorizationService)
    {
        _abpAuthorizationPolicyProvider = abpAuthorizationPolicyProvider;
        _abpAuthorizationService = abpAuthorizationService;
    }


    public async Task CheckAsync(MethodInvocationAuthorizationContext context)
    {
        // 匿名检查
        if (AllowAnonymous(context))
        {
            return;
        }


        // 从PolicyProvider中得到AuthorizationPolicy
        var authorizationPolicy = await AuthorizationPolicy.CombineAsync(
            _abpAuthorizationPolicyProvider,
            GetAuthorizationDataAttributes(context.Method)
        );


        if (authorizationPolicy == null)
        {
            return;
        }


        // 授权检查
        await _abpAuthorizationService.CheckAsync(authorizationPolicy);
    }


    protected virtual bool AllowAnonymous(MethodInvocationAuthorizationContext context)
    {
        return context.Method.GetCustomAttributes(true).OfType<IAllowAnonymous>().Any();
    }


    protected virtual IEnumerable<IAuthorizeData> GetAuthorizationDataAttributes(MethodInfo methodInfo)
    {
        var attributes = methodInfo
            .GetCustomAttributes(true)
            .OfType<IAuthorizeData>();


        if (methodInfo.IsPublic && methodInfo.DeclaringType != null)
        {
            attributes = attributes
                .Union(
                    methodInfo.DeclaringType
                        .GetCustomAttributes(true)
                        .OfType<IAuthorizeData>()
                );
        }


        return attributes;
    }
}

此处细化一下AbpAuthorizationService.CheckAsync,其实现内部最终调用到Asp.Net Core中的AuthorizationService.AuthorizeAsync,最后再进入到AuthorizationHandler中。

CheckAsync()   //MethodInvocationAuthorizationService.cs
  abpAuthorizationService.CheckAsync()   //AbpAuthorizationServiceExtensions.cs
    authorizationService.IsGrantedAsync()   //AbpAuthorizationServiceExtensions.cs
      authorizationService.AuthorizeAsync()//AbpAuthorizationServiceExtensions.cs
        authorizationService.AuthorizeAsync()  //Microsoft.AspNetCore.Authorization.cs
          PermissionRequirementHandler.HandleRequirementAsync()  //PermissionRequirementHandler.cs

该部分类图简要如下 204134337_80063fc8-30bf-470f-b715-f7d279d9cc8e

手动权限检查

在方法内部当想要检查是否拥有某权限时,还可以直接使用AuthorizationService来检查。

public async Task CreateAsync(CreateAuthorDto input)
{
    var result = await AuthorizationService
        .AuthorizeAsync("Author_Management_Create_Books");
    if (result.Succeeded == false)
    {
        //throw exception
        throw new AbpAuthorizationException("...");
    }


    //continue to the normal flow...
}

Microsoft.Asp.Net Core.Authorization.AuthorizationService在AppService中内置好了,因此直接使用即可。

public abstract class ApplicationService :
    IApplicationService,
    IAvoidDuplicateCrossCuttingConcerns,
    IValidationEnabled,
    IUnitOfWorkEnabled,
    IAuditingEnabled,
    IGlobalFeatureCheckingEnabled,
    ITransientDependency
{
  protected IAuthorizationService AuthorizationService => LazyServiceProvider.LazyGetRequiredService<IAuthorizationService>();
}

最终还是进入到PermissionRequirementHandler中或者自定义的AuthorizationHandler中。

扩展

授权这块Asp.Net Core本身提供了很多扩展,PolicyProvider和AuthorizationHandler很常用。对于ABP中,提供了一些额外的扩展。

  • 自定义PermissionCheck,类似RemotePermisionCheck,例如当权限检查在BFF层又不能访问数据库时,可以实现远程调用服务来实现检查。

  • 自定义PermissionValueProvider,提供想要的扩展。因场景需要,我这之前扩展了下菜单的权限值,对菜单进行控制,只允许具有权限和特定菜单的才能通过权限判定。类似场景都可以扩展。

2024-02-20,望技术有成后能回来看见自己的脚步。