ABP - 接口授权 [Authorize、AllowAnonymous、IPermissionChecker、PermissionDefinitionProvider]

接口授权(Authorization)

核心辅助类

  • [Authorize]:标记需要授权的接口。
  • [AllowAnonymous]:允许匿名访问。
  • IPermissionChecker:手动检查权限。
  • PermissionDefinitionProvider:定义和组织权限的推荐方式.

接口授权(Authorization)核心类示例与讲解

接口授权是在"身份认证(如JWT)"基础上,进一步控制"谁能访问哪些接口"的机制(比如"普通用户只能查看自己的订单,管理员能查看所有订单")。ABP通过[Authorize][AllowAnonymous]IPermissionChecker实现灵活的授权控制,以下结合实例详解。

一、核心概念:授权的3种常见场景

  1. 身份授权:只有登录用户才能访问(排除匿名用户);
  2. 角色授权:只有特定角色(如Admin)的用户才能访问;
  3. 权限授权:只有拥有特定权限(如Order.Delete)的用户才能访问(ABP推荐,更灵活)。

二、核心类/特性说明

类/特性 作用 适用场景
[Authorize] 标记接口需要授权才能访问 限制匿名访问,或结合角色/权限控制
[AllowAnonymous] 允许匿名访问(忽略[Authorize] 公开接口(如登录、注册)
IPermissionChecker 手动检查权限(代码中动态判断) 复杂授权逻辑(如"只能修改自己创建的数据")
PermissionDefinitionProvider 定义和组织应用程序权限 权限系统的标准定义方式

三、实操示例:从基础到进阶

1. 基础授权:禁止匿名访问([Authorize])

最简单的授权是“只有登录用户能访问”,用[Authorize]标记接口即可,未登录用户会被拒绝(返回401错误)。

示例:登录用户才能访问个人中心

using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Services;

// 整个服务类的所有方法都需要授权(登录才能访问)
[Authorize]
public class UserProfileAppService : ApplicationService
{
    // 1. 查看个人信息(继承类的[Authorize],无需重复标记)
    public async Task<ProfileDto> GetMyProfileAsync()
    {
        // 通过CurrentUser获取当前登录用户ID
        var userId = CurrentUser.Id;
        // 查询用户信息并返回...
    }

	// 2. 编辑个人信息(同样需要登录)
	public async Task UpdateMyProfileAsync(UpdateProfileInput input)
	{
	    var userId = CurrentUser.Id;
	    // 编辑逻辑...
	}

}

// 对比:公开接口(允许匿名访问)
public class PublicAppService : ApplicationService
{
    // 登录接口必须允许匿名访问,否则用户无法登录
    [AllowAnonymous]
    public async Task<LoginResultDto> LoginAsync(LoginInput input)
    {
        // 登录逻辑...
    }
}

2. 角色授权:限制特定角色访问

通过[Authorize(Roles = "角色名")]限制只有指定角色的用户才能访问(如Admin角色)。

示例:只有管理员能访问用户管理接口

// 只有Admin角色能访问这个服务的所有方法
[Authorize(Roles = "Admin")]
public class UserManagementAppService : ApplicationService
{
    // 1. 查看所有用户(仅Admin可访问)
    public async Task<List<UserDto>> GetAllUsersAsync()
    {
        // 查询所有用户逻辑...
    }

    // 2. 删除用户(仅Admin可访问)
    public async Task DeleteUserAsync(Guid userId)
    {
        // 删除逻辑...
    }
}

// 进阶:多角色授权(Admin或Manager均可访问)
[Authorize(Roles = "Admin,Manager")]
public class OrderManagementAppService : ApplicationService
{
    // 查看所有订单(Admin或Manager角色可访问)
    public async Task<List<OrderDto>> GetAllOrdersAsync()
    {
        // 逻辑...
    }
}

3. 权限授权:ABP推荐的灵活方式(重点)

ABP的权限系统比角色更灵活(一个角色可包含多个权限,一个用户可拥有多个角色),通过[Authorize(Policy = "权限名")]控制访问。

步骤1:定义权限常量(在共享项目中)

首先创建权限常量类,通常放在 Domain.Shared 项目中:

public static class MyAppPermissions
{
    // 定义权限组名称
    public const string GroupName = "MyApp";

    // 定义订单相关权限
    public static class Orders
    {
        public const string Default = GroupName + ".Orders";
        public const string Create = Default + ".Create";
        public const string Edit = Default + ".Edit";
        public const string Delete = Default + ".Delete";
    }
    
    // 定义客户相关权限
    public static class Customers
    {
        public const string Default = GroupName + ".Customers";
        public const string Create = Default + ".Create";
        public const string Edit = Default + ".Edit";
        public const string Delete = Default + ".Delete";
    }
}

步骤2:使用 PermissionDefinitionProvider 定义权限

创建 PermissionDefinitionProvider 类来正式定义权限,这比直接在模块中配置更为规范:

using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Localization;

namespace MyApp.Authorization
{
    public class MyAppPermissionDefinitionProvider : PermissionDefinitionProvider
    {
        public override void Define(IPermissionDefinitionContext context)
        {
            // 添加权限组
            var myAppGroup = context.AddGroup(MyAppPermissions.GroupName, L("Permission:MyApp"));

            // 为订单添加权限
            var orderPermission = myAppGroup.AddPermission(MyAppPermissions.Orders.Default, L("Permission:Orders"));
            orderPermission.AddChild(MyAppPermissions.Orders.Create, L("Permission:Orders.Create"));
            orderPermission.AddChild(MyAppPermissions.Orders.Edit, L("Permission:Orders.Edit"));
            orderPermission.AddChild(MyAppPermissions.Orders.Delete, L("Permission:Orders.Delete"));
    
            // 为客户添加权限
            var customerPermission = myAppGroup.AddPermission(MyAppPermissions.Customers.Default, L("Permission:Customers"));
            customerPermission.AddChild(MyAppPermissions.Customers.Create, L("Permission:Customers.Create"));
            customerPermission.AddChild(MyAppPermissions.Customers.Edit, L("Permission:Customers.Edit"));
            customerPermission.AddChild(MyAppPermissions.Customers.Delete, L("Permission:Customers.Delete"));
        }
    
        private static LocalizableString L(string name)
        {
            return LocalizableString.Create<MyAppResource>(name);
        }
    }
}

步骤3:在模块中注册 PermissionDefinitionProvider

在 Domain.Shared 项目的模块类中注册权限定义提供者:

[DependsOn(typeof(AbpAuthorizationModule))]
public class MyAppDomainSharedModule : AbpModule
{
    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        // 注册权限定义提供者
        PreConfigure<AbpPermissionOptions>(options =>
        {
            options.DefinitionProviders.Add<MyAppPermissionDefinitionProvider>();
        });
    }
}

步骤4:给角色分配权限(种子数据中)

在种子数据中给Admin角色分配权限:

[Dependency(ServiceLifetime.Transient)]
public class PermissionSeedData : IDataSeeder
{
    private readonly IPermissionManager _permissionManager;
    private readonly IRepository<IdentityRole, Guid> _roleRepo;
    private readonly IIdentityRoleManager _roleManager;

    public PermissionSeedData(
        IPermissionManager permissionManager,
        IRepository<IdentityRole, Guid> roleRepo,
        IIdentityRoleManager roleManager)
    {
        _permissionManager = permissionManager;
        _roleRepo = roleRepo;
        _roleManager = roleManager;
    }

    public async Task SeedAsync(DataSeedContext context)
    {
        // 给Admin角色分配订单删除权限
        var adminRole = await _roleRepo.FirstOrDefaultAsync(r => r.Name == "Admin");
        if (adminRole != null)
        {
            await _roleManager.AddPermissionAsync(adminRole, MyAppPermissions.Orders.Delete);
        }
    }
}

步骤5:用权限控制接口访问

现在可以在应用服务中使用权限常量进行授权控制:

public class OrderAppService : ApplicationService
{
    // 1. 创建订单:需要订单创建权限
    [Authorize(MyAppPermissions.Orders.Create)]
    public async Task<OrderDto> CreateOrderAsync(CreateOrderInput input)
    {
        // 创建逻辑...
    }

    // 2. 删除订单:需要订单删除权限
    [Authorize(MyAppPermissions.Orders.Delete)]
    public async Task DeleteOrderAsync(Guid orderId)
    {
        // 删除逻辑...
    }
    
    // 3. 编辑订单:需要订单编辑权限
    [Authorize(MyAppPermissions.Orders.Edit)]
    public async Task UpdateOrderAsync(Guid orderId, UpdateOrderInput input)
    {
        // 更新逻辑...
    }
    
    // 4. 查看订单列表:只需基本订单访问权限
    [Authorize(MyAppPermissions.Orders.Default)]
    public async Task<List<OrderDto>> GetOrdersAsync()
    {
        // 查询逻辑...
    }
}

4. 手动检查权限(IPermissionChecker)

复杂场景下(如“根据订单金额动态判断是否有权限删除”),需在代码中手动检查权限,使用IPermissionChecker

示例:订单金额大于1000时,需要特殊权限才能删除

public class OrderAppService : ApplicationService
{
    private readonly IPermissionChecker _permissionChecker; // 手动权限检查工具
    private readonly IRepository<Order, Guid> _orderRepo;

    public OrderAppService(
        IPermissionChecker permissionChecker,
        IRepository<Order, Guid> orderRepo)
    {
        _permissionChecker = permissionChecker;
        _orderRepo = orderRepo;
    }
    
    [Authorize] // 至少需要登录
    public async Task DeleteOrderAsync(Guid orderId)
    {
        var order = await _orderRepo.GetAsync(orderId);
    
        // 1. 基础检查:是否是自己的订单(数据隔离)
        if (order.CreatorId != CurrentUser.Id)
        {
            throw new UserFriendlyException("不能删除他人的订单!");
        }
    
        // 2. 动态权限检查:金额>1000需要特殊权限才能删除
        if (order.TotalAmount > 1000)
        {
            // 手动检查是否有订单删除高级权限
            var hasHighPermission = await _permissionChecker.IsGrantedAsync(MyAppPermissions.Orders.Delete);
            if (!hasHighPermission)
            {
                throw new UserFriendlyException("您没有权限删除金额大于1000的订单!");
            }
        }
    
        // 3. 执行删除
        await _orderRepo.DeleteAsync(order);
    }
}

5. 全局授权与局部匿名([AllowAnonymous]

如果整个服务类需要授权,但个别方法允许匿名访问(如“用户服务”中登录方法允许匿名),可用[AllowAnonymous]覆盖。

// 全局:整个服务需要授权
[Authorize]
public class UserAppService : ApplicationService
{
    // 例外:登录方法允许匿名访问
    [AllowAnonymous]
    public async Task<LoginResultDto> LoginAsync(LoginInput input)
    {
        // 登录逻辑...
    }

    // 其他方法:需要授权(继承类的[Authorize])
    public async Task<UserDto> GetProfileAsync()
    {
        // 逻辑...
    }
}

四、权限检查的核心原理

  1. 权限存储:ABP将权限信息存在数据库(AbpPermissions表),记录“哪个角色/用户拥有哪个权限”;
  2. 验证时机:当访问[Authorize(Policy = "权限名")]标记的接口时,ABP会自动查询当前用户的权限列表,检查是否包含所需权限;
  3. 未授权处理:若未授权,自动返回403 Forbidden错误,前端可捕获并提示“没有权限”。

五、新手避坑指南

  1. [Authorize][AllowAnonymous]的优先级:方法上的特性优先于类上的(如类标记[Authorize],方法标记[AllowAnonymous],则方法允许匿名);
  2. 权限名拼写错误[Authorize(Policy = "Order.Delete")]中的权限名必须和Define时的完全一致(大小写敏感),否则会一直提示“无权限”;
  3. 角色与权限的选择:优先用权限(更灵活),角色适合简单场景(如“管理员”“普通用户”);
  4. 数据隔离与授权结合:授权控制“能否访问接口”,但接口内部仍需判断“能否操作特定数据”(如“只能修改自己的订单”),避免越权操作。

总结

  • [Authorize]:基础授权,控制“是否需要登录”或“需要特定角色/权限”;
  • [AllowAnonymous]:例外处理,允许公开访问(如登录接口);
  • IPermissionChecker:复杂场景下手动检查权限,支持动态逻辑。

通过这三个工具,可实现从简单到复杂的接口授权控制,确保系统数据安全。需要“前端如何根据权限动态显示按钮”的示例可以告诉我,我会补充前后端联动的实现。

posted @ 2025-10-24 21:41  【唐】三三  阅读(125)  评论(0)    收藏  举报