ABP自定义权限添加和注意事项

小占时光 2025-04-07 16:56:17 21


介绍

看着网站的登录界面想从新做一个,又看到个人中心内容太少,干脆写一个用户模块,把登录和用户管理的功能优化一下。在写功能的过程使用了Permissions,之前在笔记中有过记录,不过重新整理一下,以前的都是个人笔记,只有自己看得懂,从新整理一下,时间长了也方便查找。

ABP 权限

权限控制(Authorization)是ABP一个核心功能。ABP 提供了一套基于声明式权限的灵活机制,开发者可以通过 PermissionDefinitionProvider 来集中管理权限定义,通过 [Authorize] 特性控制访问。官方文档,看完之后视乎不是我要的。还是自己写一个好。

官方文档:➤ https://abp.io/docs/latest/modules/permission-management

自定义权限

ABP是一个比较成熟的框架,对于权限肯定不需要从头写。在创建项目时,Contracts层会有一个Permissions文件夹,里面已经给出模板。如果没有这个文件夹,自己创建一个就行了。

就以我的 代码为例,需要创建一个AccountPermissionDefinitionProvider 类,名字自定义,只需要集成PermissionDefinitionProvider既可以。每个Group都可以使用AddPermission添加多个权限。

public class AccountPermissionDefinitionProvider : PermissionDefinitionProvider
{
    public override void Define(IPermissionDefinitionContext context)
    {
        var myGroup = context.AddGroup(AccountPermissions.GroupName, L("Permission:Account"));
        myGroup.AddPermission(AccountPermissions.ManageLogin, L("Permission:ManageLogin"));
    }

    private static LocalizableString L(string name)
    {
        return LocalizableString.Create<AccountResource>(name);
    }
}

而AccountPermissions内容和简单,主要就是提供Key,在使用的位置就不需要输入字符串了,这是程序员的自我修养。

ABP 还提供了多语言支持,所以为了显示对应的文字,还要在语言文件中添加对应的文字。

做好上面几步,就可以在后台管理页面得到这样的效果了。

权限的过滤

添加好权限后,最主要的还是如何使用,目前我遇到的方式有三种:

第一种:

直接在类上面添加标签,默认是的类中的所有方法都会判断权限

[Authorize(AccountPermissions.ManageLogin)]
public class ThirdPartyAdminAppService : CrudAppService<ThirdParty, ...>, IThirdPartyAdminAppService
{
    public ThirdPartyAdminAppService(IRepository<ThirdParty, Guid> repository) : base(repository)
    {

    }
}

第二种:

在构造函数中添加

public class ThirdPartyAdminAppService : CrudAppService<ThirdParty, ...>, IThirdPartyAdminAppService
{
    public ThirdPartyAdminAppService(IRepository<ThirdParty, Guid> repository) : base(repository)
    {
        GetListPolicyName = AccountPermissions.ManageLogin;
        CreatePolicyName = AccountPermissions.ManageLogin;
        UpdatePolicyName = AccountPermissions.ManageLogin;
        DeletePolicyName = AccountPermissions.ManageLogin;
    }
}

第三种:

直接在类中指定方法上面添加

 [Authorize(AccountPermissions.ManageLogin)]
  public async Task EnableAsync(Guid id)
  {

  }

添加了权限之后,再次访问指定的类或者方法,有权限的则可以正常访问没有权限的则会报异常。网站是可以统一跳转错误提示页面的。

踩过的坑

上面的内容都是比较容易得,照着做就行了,但是问题在于,照着做了,却不一定生效。如果你写了一个类ThirdPartyAdminAppService和上面样集成CrudAppService,再加了权限限制,那基本着权限判断会出现有的生效有的不生效,即使没有权限的人也能访问。我第一此遇到的这个问题时,也是一头雾水,问过AI,AI乱回答一些,参照AI的做法基本不解决问题,也许AI辅助编程目前的问题就是在遇到框架问题上,很难有好的回答。

下面就先给出答案:

方法类型 是否会被 ABP 权限拦截器拦截
public virtual ✅ 会拦截
public override ❌ 不会拦截
publicvirtual ❌ 不会拦截
private / static 方法 ❌ 不会拦截

通常我们为了方便会直接继承ABP 服务类CrudAppService 这样可以减少很多重复代码,但这样一来,很多方法就需要override ,有一些方法我们又是我们自己写,这就导致了加了override 的方法不能被拦截,而我们自己写的方法又可以正常拦截。对于不了解的开发人员,这个问题,这是会让人头大。

原理分析

遇到这个问题的原因是,底层框架的限制,即使在override 的方法加 [Authorize] 特性也会失效。这是Castle DynamicProxy(ABP 使用的 AOP 框架)天然的限制。

Castle 动态代理在运行时会生成一个代理类,它只能拦截 virtual 方法。如果你重写了一个基类方法(用 override),它拦截的其实是基类的方法,而不是子类的。Castle 是通过 创建子类并重写虚方法来注入拦截器 的,但你写了 override 后,相当于 ABP 的代理子类没有办法再重写这个方法了(因为你已经占用了这个重写机会)。

为什么 public async Task<LoginBackgroundDto> CreateAsync(string url) 这样写的方法能被拦截,而我没有写 virtual?看起来没有写 virtual,但实际上编译器在生成代码时,会自动把没有标记为 override 的方法视为 virtual。像下面这样调试一下就能看到方法的属性了。

解决方法

在开发过程中,我们不可以避免使用框架提供能的基类,这样可以节省很多重复工作,那要如果避免上面的问题呢?

使用使用第二种写法:

public class ThirdPartyAdminAppService : CrudAppService<ThirdParty, ...>, IThirdPartyAdminAppService
{
    public ThirdPartyAdminAppService(IRepository<ThirdParty, Guid> repository) : base(repository)
    {
        GetListPolicyName = AccountPermissions.ManageLogin;
        CreatePolicyName = AccountPermissions.ManageLogin;
        UpdatePolicyName = AccountPermissions.ManageLogin;
        DeletePolicyName = AccountPermissions.ManageLogin;
    }
}

这种写法,会在基类方法执行时判断权限,看一下GetListAsync源码就知道,在执行前会先 CheckGetListPolicyAsync 判断GetListPolicyName 权限,其他方法也是这样。这样权限过滤就会生效了。

public virtual async Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input)
{
    await CheckGetListPolicyAsync();

    var query = await CreateFilteredQueryAsync(input);
    var totalCount = await AsyncExecuter.CountAsync(query);

    var entities = new List<TEntity>();
    var entityDtos = new List<TGetListOutputDto>();

    if (totalCount > 0)
    {
        query = ApplySorting(query, input);
        query = ApplyPaging(query, input);

        entities = await AsyncExecuter.ToListAsync(query);
        entityDtos = await MapToGetListOutputDtosAsync(entities);
    }

    return new PagedResultDto<TGetListOutputDto>(
        totalCount,
        entityDtos
    );
}

protected virtual async Task CheckGetListPolicyAsync()
{
    await CheckPolicyAsync(GetListPolicyName);
}

最后一次修改 : 2025/4/16 下午11:25:09

优秀
123
分享
123
奶茶
123
文章版权声明:版权归原作者(小占时光)所有。未经明确书面许可,严禁任何个人或组织以任何形式进行商业性或非商业性的使用、转载或抄袭。
评论