接口授权(Authorization)
核心辅助类:
[Authorize]:标记需要授权的接口。[AllowAnonymous]:允许匿名访问。IPermissionChecker:手动检查权限。
接口授权(Authorization)核心类示例与讲解
接口授权是在“身份认证(如JWT)”基础上,进一步控制“谁能访问哪些接口”的机制(比如“普通用户只能查看自己的订单,管理员能查看所有订单”)。ABP通过[Authorize]、[AllowAnonymous]和IPermissionChecker实现灵活的授权控制,以下结合实例详解。
一、核心概念:授权的3种常见场景
- 身份授权:只有登录用户才能访问(排除匿名用户);
- 角色授权:只有特定角色(如
Admin)的用户才能访问; - 权限授权:只有拥有特定权限(如
Order.Delete)的用户才能访问(ABP推荐,更灵活)。
二、核心类/特性说明
| 类/特性 | 作用 | 适用场景 |
|---|---|---|
[Authorize] |
标记接口需要授权才能访问 | 限制匿名访问,或结合角色/权限控制 |
[AllowAnonymous] |
允许匿名访问(忽略[Authorize]) |
公开接口(如登录、注册) |
IPermissionChecker |
手动检查权限(代码中动态判断) | 复杂授权逻辑(如“只能修改自己创建的数据”) |
三、实操示例:从基础到进阶
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获取当前登录用户IDvar 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:定义权限(在模块中)
首先在领域模块(如MyAppDomainModule)中定义权限:
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Modularity;public class MyAppDomainModule : AbpModule
{public override void ConfigureServices(ServiceConfigurationContext context){// 配置权限Configure<AbpPermissionOptions>(options =>{// 1. 定义权限组(如“订单管理”)options.DefinePermissionGroup("OrderManagement");// 2. 在组内定义具体权限options.Define("Order.Create", // 权限名(唯一标识)displayName: "创建订单", // 显示名(UI中展示)description: "允许创建新订单", // 描述groupName: "OrderManagement" // 所属组);options.Define("Order.Delete", displayName: "删除订单", description: "允许删除订单", groupName: "OrderManagement");});}
}
步骤2:给角色分配权限(种子数据中)
在种子数据中给Admin角色分配Order.Delete权限(普通用户不给):
[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角色分配Order.Delete权限var adminRole = await _roleRepo.FirstOrDefaultAsync(r => r.Name == "Admin");if (adminRole != null){await _roleManager.AddPermissionAsync(adminRole, "Order.Delete");}}
}
步骤3:用权限控制接口访问
public class OrderAppService : ApplicationService
{// 1. 创建订单:所有登录用户都能访问(需要Order.Create权限)[Authorize(Policy = "Order.Create")]public async Task<OrderDto> CreateOrderAsync(CreateOrderInput input){// 创建逻辑...}// 2. 删除订单:只有拥有Order.Delete权限的用户能访问(如Admin)[Authorize(Policy = "Order.Delete")]public async Task DeleteOrderAsync(Guid orderId){// 删除逻辑...}// 3. 查看自己的订单:登录用户即可(无需特定权限,但需过滤数据)[Authorize]public async Task<List<OrderDto>> GetMyOrdersAsync(){var userId = CurrentUser.Id;// 只查询当前用户的订单var orders = await _orderRepo.GetListAsync(o => o.CreatorId == userId);return ObjectMapper.Map<List<Order>, List<OrderDto>>(orders);}
}
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需要Order.Delete.High权限if (order.TotalAmount > 1000){// 手动检查是否有Order.Delete.High权限var hasHighPermission = await _permissionChecker.IsGrantedAsync("Order.Delete.High");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(){// 逻辑...}
}
四、权限检查的核心原理
- 权限存储:ABP将权限信息存在数据库(
AbpPermissions表),记录“哪个角色/用户拥有哪个权限”; - 验证时机:当访问
[Authorize(Policy = "权限名")]标记的接口时,ABP会自动查询当前用户的权限列表,检查是否包含所需权限; - 未授权处理:若未授权,自动返回403 Forbidden错误,前端可捕获并提示“没有权限”。
五、新手避坑指南
[Authorize]与[AllowAnonymous]的优先级:方法上的特性优先于类上的(如类标记[Authorize],方法标记[AllowAnonymous],则方法允许匿名);- 权限名拼写错误:
[Authorize(Policy = "Order.Delete")]中的权限名必须和Define时的完全一致(大小写敏感),否则会一直提示“无权限”; - 角色与权限的选择:优先用权限(更灵活),角色适合简单场景(如“管理员”“普通用户”);
- 数据隔离与授权结合:授权控制“能否访问接口”,但接口内部仍需判断“能否操作特定数据”(如“只能修改自己的订单”),避免越权操作。
总结
[Authorize]:基础授权,控制“是否需要登录”或“需要特定角色/权限”;[AllowAnonymous]:例外处理,允许公开访问(如登录接口);IPermissionChecker:复杂场景下手动检查权限,支持动态逻辑。
通过这三个工具,可实现从简单到复杂的接口授权控制,确保系统数据安全。需要“前端如何根据权限动态显示按钮”的示例可以告诉我,我会补充前后端联动的实现。
