ABP演示 - 授权
ABP v8.1 授权系统完整指南 - Publishers模块实战示例
本指南详细演示如何为ABP BookStore项目的Publishers模块添加完整的权限控制,涵盖从权限定义到实际应用的全流程。

📚 目录
概述
ABP授权系统简介
ABP的授权系统基于ASP.NET Core的授权机制,并提供了权限系统来简化权限管理。它支持:
- 声明式授权:使用
[Authorize]特性 - 隐式授权:在
CrudAppService中配置策略名称 - 代码级授权:使用
AuthorizationService进行灵活检查 - 基于资源的授权:根据资源本身进行权限判断
本示例目标
为Publishers模块实现以下功能:
| 功能 | 权限名称 | 说明 |
|---|---|---|
| 查看出版商 | BookStore.Publishers |
查看列表和详情 |
| 创建出版商 | BookStore.Publishers.Create |
创建新出版商 |
| 编辑出版商 | BookStore.Publishers.Edit |
更新出版商信息 |
| 删除出版商 | BookStore.Publishers.Delete |
删除出版商 |
权限定义与注册
步骤 1: 定义权限常量
文件: src/Acme.BookStore.Application.Contracts/Permissions/BookStorePermissions.cs

public static class Publishers
{
public const string Default = GroupName + ".Publishers";
public const string Create = Default + ".Create";
public const string Edit = Default + ".Edit";
public const string Delete = Default + ".Delete";
}
命名约定:
- 格式:
{GroupName}.{Entity}.{Action} - 示例:
BookStore.Publishers.Create - 好处:清晰、有层次、避免冲突
步骤 2: 注册权限
文件: src/Acme.BookStore.Application.Contracts/Permissions/BookStorePermissionDefinitionProvider.cs

public override void Define(IPermissionDefinitionContext context)
{
var bookStoreGroup = context.AddGroup(
BookStorePermissions.GroupName,
L("Permission:BookStore"));
// 添加Publishers父权限
var publishersPermission = bookStoreGroup.AddPermission(
BookStorePermissions.Publishers.Default,
L("Permission:Publishers"));
// 添加子权限
publishersPermission.AddChild(
BookStorePermissions.Publishers.Create,
L("Permission:Publishers.Create"));
publishersPermission.AddChild(
BookStorePermissions.Publishers.Edit,
L("Permission:Publishers.Edit"));
publishersPermission.AddChild(
BookStorePermissions.Publishers.Delete,
L("Permission:Publishers.Delete"));
}
权限层次结构:
BookStore (权限组)
└── Publishers (父权限)
├── Create (子权限)
├── Edit (子权限)
└── Delete (子权限)
步骤 3: 添加本地化文本
文件: src/Acme.BookStore.Domain.Shared/Localization/BookStore/zh-Hans.json
{
"texts": {
"Permission:BookStore": "书店管理",
"Permission:Publishers": "出版商管理",
"Permission:Publishers.Create": "创建新出版商",
"Permission:Publishers.Edit": "编辑出版商",
"Permission:Publishers.Delete": "删除出版商"
}
}
权限的三种使用方式
方式 1: 隐式使用(CrudAppService 自动处理)
适用场景:标准的 CRUD 操作
工作原理:
在构造函数中设置权限策略名称后,CrudAppService 基类会自动应用授权检查。
代码示例:
public class PublisherAppService : CrudAppService<
Publisher, PublisherDto, Guid, GetPublisherDto,
CreateUpdatePublisherDto, CreateUpdatePublisherDto>,
IPublisherAppService
{
public PublisherAppService(
IRepository<Publisher, Guid> repository,
IPublisherRepository publisherRepository,
IOptions<PublisherOptions> publisherOptions,
ILocalEventBus localEventBus)
: base(repository)
{
_publisherRepository = publisherRepository;
_publisherOptions = publisherOptions.Value;
_localEventBus = localEventBus;
// 🎯 配置权限策略 - 基类会自动使用这些配置!
GetPolicyName = BookStorePermissions.Publishers.Default;
GetListPolicyName = BookStorePermissions.Publishers.Default;
CreatePolicyName = BookStorePermissions.Publishers.Create;
UpdatePolicyName = BookStorePermissions.Publishers.Edit;
DeletePolicyName = BookStorePermissions.Publishers.Delete;
}
// 以下方法会自动检查权限(不需要手动添加 [Authorize])
public override async Task<PublisherDto> GetAsync(Guid id)
{
// 自动检查: BookStore.Publishers.Default 权限
// 如果用户没有权限,会自动返回 403 Forbidden
}
public override async Task<PagedResultDto<PublisherDto>> GetListAsync(GetPublisherDto input)
{
// 自动检查: BookStore.Publishers.Default 权限
}
public override async Task<PublisherDto> CreateAsync(CreateUpdatePublisherDto input)
{
// 自动检查: BookStore.Publishers.Create 权限
}
public override async Task<PublisherDto> UpdateAsync(Guid id, CreateUpdatePublisherDto input)
{
// 自动检查: BookStore.Publishers.Edit 权限
}
public override async Task DeleteAsync(Guid id)
{
// 自动检查: BookStore.Publishers.Delete 权限
}
}

内部机制(ABP源码简化版):
// CrudAppService 基类内部逻辑
[Authorize]
public virtual async Task<TEntityDto> CreateAsync(TCreateInput input)
{
await CheckCreatePolicyAsync(); // 检查权限
// ... 执行创建逻辑
}
protected virtual async Task CheckCreatePolicyAsync()
{
if (CreatePolicyName != null)
{
await AuthorizationService.CheckAsync(CreatePolicyName);
}
}
优点:
- ✅ 代码简洁,不需要手动添加特性
- ✅ 符合 DRY 原则
- ✅ 统一的权限检查逻辑
缺点:
- ❌ 只能在
CrudAppService中使用 - ❌ 灵活性较低
方式 2: 显式使用 [Authorize] 特性
适用场景:自定义的非 CRUD 方法
代码示例:
using Microsoft.AspNetCore.Authorization;
public class PublisherAppService : ApplicationService
{
/// <summary>
/// 批量删除出版商(需要删除权限)
/// </summary>
[Authorize(BookStorePermissions.Publishers.Delete)]
public async Task BatchDeleteAsync(List<Guid> ids)
{
foreach (var id in ids)
{
await Repository.DeleteAsync(id);
}
}
/// <summary>
/// 允许匿名访问(通常用于公开API)
/// </summary>
[AllowAnonymous]
public async Task<PublisherDto> GetPublicPublisherAsync(Guid id)
{
// 任何人都可以访问
}
/// <summary>
/// 整个服务都需要登录
/// </summary>
[Authorize]
public class PublisherAppService : ApplicationService
{
// 所有方法都需要登录
}
}
优点:
- ✅ 声明式,代码清晰
- ✅ 易于理解和维护
- ✅ 符合 ASP.NET Core 的授权模式
缺点:
- ❌ 无法在方法内部添加权限相关的业务逻辑
- ❌ 无法根据权限返回不同的数据
方式 3: 使用 AuthorizationService
适用场景:需要在代码中进行灵活的权限检查
3.1 使用 CheckAsync(强制检查)
using Volo.Abp.Authorization;
public class PublisherAppService : ApplicationService
{
/// <summary>
/// 获取出版商统计信息(需要查看权限)
/// </summary>
public async Task<string> GetPublisherStatisticsAsync()
{
// 检查权限,如果没有权限会抛出 AbpAuthorizationException
await AuthorizationService.CheckAsync(BookStorePermissions.Publishers.Default);
var totalCount = await Repository.CountAsync();
var recentCount = await Repository.CountAsync(
p => p.CreationTime > DateTime.Now.AddDays(-30));
return $"总出版商数: {totalCount}, 最近30天新增: {recentCount}";
}
}
特点:
- 无权限时抛出
AbpAuthorizationException异常 - 异常会被ABP框架捕获并转换为
403 Forbidden响应 - 适用于需要强制权限检查的场景
3.2 使用 IsGrantedAsync(条件检查)
using Volo.Abp.Authorization;
public class PublisherAppService : ApplicationService
{
/// <summary>
/// 获取带权限信息的出版商详情
/// </summary>
public async Task<PublisherDto> GetPublisherWithDetailsAsync(Guid id)
{
var publisher = await Repository.GetAsync(id);
var dto = ObjectMapper.Map<Publisher, PublisherDto>(publisher);
// 检查用户是否有编辑权限(不会抛出异常)
bool canEdit = await AuthorizationService.IsGrantedAsync(
BookStorePermissions.Publishers.Edit);
dto.CanEdit = canEdit;
// 检查用户是否有删除权限
bool canDelete = await AuthorizationService.IsGrantedAsync(
BookStorePermissions.Publishers.Delete);
dto.CanDelete = canDelete;
return dto;
}
}
特点:
- 不抛出异常,返回
true或false - 可以根据权限返回不同的数据
- 这是前端UI控制按钮显示的关键!
前端使用示例:
// 在 Angular/React/Vue 前端
const publisher = await publisherService.getPublisherWithDetailsAsync(id);
if (publisher.canEdit) {
// 显示编辑按钮
showEditButton = true;
}
if (publisher.canDelete) {
// 显示删除按钮
showDeleteButton = true;
}
3.3 在循环中检查权限
public async Task<List<string>> BatchExportAsync(List<Guid> ids)
{
var results = new List<string>();
foreach (var id in ids)
{
var publisher = await Repository.GetAsync(id);
// 检查用户是否有权限查看这个特定的出版商
if (await AuthorizationService.IsGrantedAsync(
BookStorePermissions.Publishers.Default))
{
results.Add(publisher.Name);
}
else
{
// 跳过无权限的项,而不是直接失败
results.Add($"[无权限查看] {publisher.Name}");
}
}
return results;
}
优点:
- ✅ 最灵活,可以完全控制权限检查逻辑
- ✅ 可以根据权限返回不同的响应
- ✅ 适合复杂的业务场景
- ✅ 可以在循环中进行条件性检查
缺点:
- ❌ 需要手动编写检查代码
- ❌ 代码量相对较多
使用方式对比
| 使用方式 | 适用场景 | 示例方法 | 优点 | 缺点 |
|---|---|---|---|---|
| 隐式使用 | CRUD 操作 | CreateAsync, UpdateAsync, DeleteAsync, GetListAsync |
简洁,无需手动添加 | 只能在 CrudAppService 中使用,灵活性低 |
| [Authorize] 特性 | 自定义 API 方法 | BatchDeleteAsync, ExportDataAsync |
声明式,代码清晰 | 无法在方法内部添加逻辑 |
| AuthorizationService.CheckAsync | 需要强制权限检查 | GetStatisticsAsync, GetReportAsync |
灵活,可添加业务逻辑 | 需要手动编写检查代码 |
| AuthorizationService.IsGrantedAsync | 需要条件性逻辑 | GetWithPermissionsAsync, BatchExportAsync |
最灵活,可返回不同数据 | 代码量相对较多 |
完整代码示例
1. 权限定义
BookStorePermissions.cs
namespace Acme.BookStore.Permissions;
public static class BookStorePermissions
{
public const string GroupName = "BookStore";
public static class Publishers
{
public const string Default = GroupName + ".Publishers";
public const string Create = Default + ".Create";
public const string Edit = Default + ".Edit";
public const string Delete = Default + ".Delete";
}
}
2. 权限注册
BookStorePermissionDefinitionProvider.cs
public override void Define(IPermissionDefinitionContext context)
{
var bookStoreGroup = context.AddGroup(
BookStorePermissions.GroupName,
L("Permission:BookStore"));
var publishersPermission = bookStoreGroup.AddPermission(
BookStorePermissions.Publishers.Default,
L("Permission:Publishers"));
publishersPermission.AddChild(
BookStorePermissions.Publishers.Create,
L("Permission:Publishers.Create"));
publishersPermission.AddChild(
BookStorePermissions.Publishers.Edit,
L("Permission:Publishers.Edit"));
publishersPermission.AddChild(
BookStorePermissions.Publishers.Delete,
L("Permission:Publishers.Delete"));
}
3. 应用服务
PublisherAppService.cs
using Acme.BookStore.Authors;
using Acme.BookStore.Options;
using Acme.BookStore.Permissions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Authorization;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.EventBus.Local;
namespace Acme.BookStore.Publishers
{
public class PublisherAppService : CrudAppService<
Publisher,
PublisherDto,
Guid,
GetPublisherDto,
CreateUpdatePublisherDto,
CreateUpdatePublisherDto>,
IPublisherAppService
{
private readonly IPublisherRepository _publisherRepository;
private readonly PublisherOptions _publisherOptions;
private readonly ILocalEventBus _localEventBus;
public PublisherAppService(
IRepository<Publisher, Guid> repository,
IPublisherRepository publisherRepository,
IOptions<PublisherOptions> publisherOptions,
ILocalEventBus localEventBus)
: base(repository)
{
_publisherRepository = publisherRepository;
_publisherOptions = publisherOptions.Value;
_localEventBus = localEventBus;
// 🎯 隐式使用:配置权限策略名称
GetPolicyName = BookStorePermissions.Publishers.Default;
GetListPolicyName = BookStorePermissions.Publishers.Default;
CreatePolicyName = BookStorePermissions.Publishers.Create;
UpdatePolicyName = BookStorePermissions.Publishers.Edit;
DeletePolicyName = BookStorePermissions.Publishers.Delete;
}
// CRUD方法会自动应用权限检查(隐式方式)
public override async Task<PublisherDto> CreateAsync(CreateUpdatePublisherDto input)
{
var publisher = new Publisher(
GuidGenerator.Create(),
input.Name,
input.EstablishmentTime,
new Address(
input.Address.Street,
input.Address.City,
input.Address.StateOrProvince,
input.Address.PostalCode,
input.Address.Country
),
input.Description,
input.Url
);
await Repository.InsertAsync(publisher);
return ObjectMapper.Map<Publisher, PublisherDto>(publisher);
}
public override async Task<PagedResultDto<PublisherDto>> GetListAsync(GetPublisherDto input)
{
if (input.Sorting.IsNullOrWhiteSpace())
{
input.Sorting = _publisherOptions.DefaultSorting;
}
var maxResultCount = input.MaxResultCount > 0
? Math.Min(input.MaxResultCount, _publisherOptions.MaxPageSize)
: _publisherOptions.DefaultPageSize;
var publishers = await _publisherRepository.GetListAsync(
input.SkipCount,
maxResultCount,
input.Sorting,
input.Filter
);
var totalCount = input.Filter == null
? await _publisherRepository.CountAsync()
: await _publisherRepository.CountAsync(
publisher => publisher.Name.Contains(input.Filter));
return new PagedResultDto<PublisherDto>(
totalCount,
ObjectMapper.Map<List<Publisher>, List<PublisherDto>>(publishers)
);
}
// 🎯 显式使用1:使用 [Authorize] 特性
[Authorize(BookStorePermissions.Publishers.Delete)]
public async Task BatchDeleteAsync(List<Guid> ids)
{
foreach (var id in ids)
{
await Repository.DeleteAsync(id);
}
}
// 🎯 显式使用2:使用 AuthorizationService.CheckAsync
public async Task<string> GetPublisherStatisticsAsync()
{
await AuthorizationService.CheckAsync(BookStorePermissions.Publishers.Default);
var totalCount = await Repository.CountAsync();
var recentCount = await Repository.CountAsync(
p => p.CreationTime > DateTime.Now.AddDays(-30));
return $"总出版商数: {totalCount}, 最近30天新增: {recentCount}";
}
// 🎯 显式使用3:使用 AuthorizationService.IsGrantedAsync
public async Task<PublisherDto> GetPublisherWithDetailsAsync(Guid id)
{
var publisher = await Repository.GetAsync(id);
var dto = ObjectMapper.Map<Publisher, PublisherDto>(publisher);
dto.CanEdit = await AuthorizationService.IsGrantedAsync(
BookStorePermissions.Publishers.Edit);
dto.CanDelete = await AuthorizationService.IsGrantedAsync(
BookStorePermissions.Publishers.Delete);
return dto;
}
}
}
4. DTO定义
PublisherDto.cs
public class PublisherDto : FullAuditedEntityDto<Guid>
{
public string Name { get; set; }
public string Description { get; set; }
public DateTime EstablishmentTime { get; set; }
public string Address { get; set; }
public string Url { get; set; }
/// <summary>
/// 当前用户是否可以编辑此出版商
/// </summary>
public bool CanEdit { get; set; }
/// <summary>
/// 当前用户是否可以删除此出版商
/// </summary>
public bool CanDelete { get; set; }
}
5. 接口定义
IPublisherAppService.cs
public interface IPublisherAppService : IApplicationService
{
Task<PagedResultDto<PublisherDto>> GetListAsync(GetPublisherDto input);
/// <summary>
/// 批量删除出版商(需要删除权限)
/// </summary>
Task BatchDeleteAsync(List<Guid> ids);
/// <summary>
/// 获取出版商统计信息(需要查看权限)
/// </summary>
Task<string> GetPublisherStatisticsAsync();
/// <summary>
/// 获取带权限信息的出版商详情
/// </summary>
Task<PublisherDto> GetPublisherWithDetailsAsync(Guid id);
}
权限测试
1. 在管理后台设置权限
- 以管理员身份登录
- 导航到 用户管理 或 角色管理
- 选择要设置的用户或角色
- 在权限选项卡中,找到 BookStore > 出版商管理
- 勾选相应的权限复选框
- 保存设置
界面效果:
权限管理
├─ BookStore (权限组)
│ ├─ 图书管理
│ ├─ 作者管理
│ └─ 出版商管理 ← 点击展开
│ ├─ ☑ 创建新出版商
│ ├─ ☑ 编辑出版商
│ └─ ☑ 删除出版商
2. 测试API端点
测试获取列表(有权限)
curl -X GET "https://localhost:44300/api/app/publisher" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# 响应:200 OK
{
"totalCount": 10,
"items": [...]
}
测试创建出版商(无权限)
curl -X POST "https://localhost:44300/api/app/publisher" \
-H "Authorization: Bearer TOKEN_WITHOUT_CREATE_PERMISSION" \
-H "Content-Type: application/json" \
-d '{
"name": "示例出版社",
"establishmentTime": "2024-01-01T00:00:00Z",
"address": {
"street": "人民路123号",
"city": "北京市",
"stateOrProvince": "北京市",
"postalCode": "100000",
"country": "中国"
}
}'
# 响应:403 Forbidden
{
"error": {
"code": "Volo.Abp.Authorization.AbpAuthorizationException",
"message": "授权失败!您的权限不足:需要权限 'BookStore.Publishers.Create'",
"details": "..."
}
}
3. 查看日志
在日志中搜索权限检查相关的日志:
[INF] Authorization: Checking permission: BookStore.Publishers.Delete
[WRN] Authorization: Permission 'BookStore.Publishers.Delete' was not granted for user: john.doe
权限系统高级配置与扩展
一、权限系统的高级配置
1.1 权限多租户细分配置
ABP 官方支持通过 multiTenancySide 参数指定权限适用范围,这对于多租户应用非常重要。
MultiTenancySides 枚举值:
Host- 仅适用于主机(租户管理员)Tenant- 仅适用于租户Both- 适用于主机和租户(默认值)
代码示例:
public override void Define(IPermissionDefinitionContext context)
{
var bookStoreGroup = context.AddGroup(
BookStorePermissions.GroupName,
L("Permission:BookStore"));
// 仅主机管理员可以访问的权限
var hostOnlyPermission = bookStoreGroup.AddPermission(
"BookStore.HostOnly",
L("Permission:HostOnly"));
hostOnlyPermission.MultiTenancySide = MultiTenancySides.Host;
// 仅租户用户可以访问的权限
var tenantOnlyPermission = bookStoreGroup.AddPermission(
"BookStore.TenantOnly",
L("Permission:TenantOnly"));
tenantOnlyPermission.MultiTenancySide = MultiTenancySides.Tenant;
// 主机和租户都可以访问的权限(默认)
var bothPermission = bookStoreGroup.AddPermission(
"BookStore.Publishers.Default",
L("Permission:Publishers"));
bothPermission.MultiTenancySide = MultiTenancySides.Both;
}
多租户场景适配:
- 主机管理员权限:系统级配置、租户管理、模块安装等
- 租户权限:业务数据操作、用户管理等
- 共享权限:通用功能,如查看权限等
1.2 权限启用 / 禁用控制
ABP 允许通过 isEnabled: false 禁用权限,禁用后所有用户无法使用该权限,权限检查会返回 false。
禁用权限:
public override void Define(IPermissionDefinitionContext context)
{
var bookStoreGroup = context.AddGroup(
BookStorePermissions.GroupName,
L("Permission:BookStore"));
// 禁用某个权限(例如:功能未完成时)
var disabledPermission = bookStoreGroup.AddPermission(
"BookStore.FeatureNotReady",
L("Permission:FeatureNotReady"));
disabledPermission.IsEnabled = false;
}
修改依赖模块的权限:
public override void Define(IPermissionDefinitionContext context)
{
// 获取并修改 ABP 框架内置权限
var deleteRolePermission = context.GetPermissionOrNull(IdentityPermissions.Roles.Delete);
if (deleteRolePermission != null)
{
// 禁用删除角色的权限(例如:不允许删除系统角色)
deleteRolePermission.IsEnabled = false;
}
}
适用场景:
- 功能未完成时临时禁用
- 特定环境下禁用某些功能
- 限制对框架内置权限的使用
二、权限扩展与自定义核心能力
2.1 Permission Value Providers 扩展
ABP 官方提供 3 种预定义 Provider,并支持自定义 Provider 来扩展权限授予逻辑。
预定义 Provider:
UserPermissionValueProvider- 基于用户授予权限RolePermissionValueProvider- 基于角色授予权限ClientPermissionValueProvider- 基于客户端(应用)授予权限
自定义 Provider 示例:
using Volo.Abp.Authorization;
using Volo.Abp.Security.Claims;
using Volo.Abp.Users;
namespace Acme.BookStore.Authorization
{
public class DepartmentPermissionValueProvider : PermissionValueProvider
{
public override string Name => "Department";
protected override async Task<PermissionGrantResult> CheckAsync(PermissionValueCheckContext context)
{
var userId = context.Principal?.FindUserId();
if (userId == null)
{
return PermissionGrantResult.Undefined;
}
// 从用户声明中获取部门信息
var departmentClaim = context.Principal.FindFirst("Department");
if (departmentClaim == null)
{
return PermissionGrantResult.Undefined;
}
// 根据部门授予特定权限
if (departmentClaim.Value == "IT" &&
context.Permission.Name == "BookStore.TechnicalReports")
{
return PermissionGrantResult.Granted;
}
return PermissionGrantResult.Undefined;
}
}
}
注册自定义 Provider:
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpPermissionOptions>(options =>
{
options.ValueProviders.Add<DepartmentPermissionValueProvider>();
});
}
PermissionGrantResult 枚举值:
Granted- 权限已授予Denied- 权限被拒绝Undefined- 未定义(继续检查其他 Provider)
2.2 自定义声明(IAbpClaimsPrincipalContributor)
ABP 支持通过实现 IAbpClaimsPrincipalContributor 接口添加自定义声明,这些声明可以用于权限检查或其他业务逻辑。
添加自定义声明示例:
using System.Security.Claims;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Security.Claims;
namespace Acme.BookStore.Security
{
public class CustomClaimsPrincipalContributor : IAbpClaimsPrincipalContributor, ITransientDependency
{
public async Task ContributeAsync(AbpClaimsPrincipalContributorContext context)
{
var userId = context.ClaimsPrincipal?.FindUserId();
if (userId == null)
{
return;
}
// 从数据库获取用户额外信息
var user = await UserRepository.GetAsync(userId.Value);
// 添加自定义声明
context.Claims.Add(new Claim("Department", user.Department));
context.Claims.Add(new Claim("EmployeeId", user.EmployeeId));
context.Claims.Add(new Claim("UserType", user.Type.ToString()));
}
}
}
配置声明服务:
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpClaimsServiceOptions>(options =>
{
options.AddContributor<CustomClaimsPrincipalContributor>();
});
}
使用自定义声明:
public async Task<bool> CanAccessDepartmentReportAsync()
{
var department = CurrentUser.FindClaim("Department")?.Value;
return department == "Finance";
}
2.3 自定义策略覆盖权限
ABP 允许注册与权限名相同的 ASP.NET Core 策略,覆盖已有权限逻辑。
自定义策略示例:
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.DependencyInjection;
namespace Acme.BookStore.Authorization
{
public class TimeBasedPermissionHandler : AuthorizationHandler<PermissionRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
PermissionRequirement requirement)
{
// 仅在工作时间(9:00-18:00)授予权限
var now = DateTime.Now;
var isWorkingHours = now.Hour >= 9 && now.Hour < 18;
if (!isWorkingHours)
{
context.Fail("非工作时间,无法执行此操作");
return Task.CompletedTask;
}
context.Succeed(requirement);
return Task.CompletedTask;
}
}
}
注册自定义策略:
public override void ConfigureServices(ServiceConfigurationContext context)
{
var services = context.Services;
services.AddAuthorization(options =>
{
options.AddPolicy("BookStore.Publishers.Create", policy =>
{
policy.Requirements.Add(new PermissionRequirement());
policy.AddRequirements(new TimeBasedRequirement());
});
});
services.AddSingleton<IAuthorizationHandler, TimeBasedPermissionHandler>();
}
三、高级组件与特殊场景
3.1 Permission Store 接口
IPermissionStore 是权限数据持久化的核心接口,ABP 默认提供数据库实现(由权限管理模块提供)。
接口定义:
public interface IPermissionStore
{
Task<bool> IsGrantedAsync(string name, string providerName, string providerKey);
Task<MultiplePermissionValueProviderResult> IsGrantedAsync(string[] names);
Task<PermissionGrantResult> IsGrantedAsync(string name, string providerName, string providerKey);
}
自定义 Permission Store 示例(例如:使用 Redis 缓存):
using System.Threading.Tasks;
using Volo.Abp.Authorization;
using Volo.Abp.Caching;
using StackExchange.Redis;
namespace Acme.BookStore.Authorization
{
public class RedisPermissionStore : IPermissionStore, ITransientDependency
{
private readonly IDistributedCache<PermissionCacheItem> _cache;
private readonly IConnectionMultiplexer _redis;
public RedisPermissionStore(
IDistributedCache<PermissionCacheItem> cache,
IConnectionMultiplexer redis)
{
_cache = cache;
_redis = redis;
}
public async Task<bool> IsGrantedAsync(
string name,
string providerName,
string providerKey)
{
var cacheKey = $"Permission:{providerName}:{providerKey}:{name}";
var cached = await _cache.GetAsync(cacheKey);
return cached?.IsGranted ?? false;
}
public async Task<MultiplePermissionValueProviderResult> IsGrantedAsync(string[] names)
{
var result = new MultiplePermissionValueProviderResult();
foreach (var name in names)
{
var isGranted = await IsGrantedAsync(name, null, null);
result.Result[name] = isGranted ? PermissionGrantResult.Granted : PermissionGrantResult.Undefined;
}
return result;
}
public async Task<PermissionGrantResult> IsGrantedAsync(
string name,
string providerName,
string providerKey)
{
var isGranted = await IsGrantedAsync(name, providerName, providerKey);
return isGranted ? PermissionGrantResult.Granted : PermissionGrantResult.Undefined;
}
}
}
注册自定义 Store:
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.Replace<IPermissionStore, RedisPermissionStore>();
}
3.2 AlwaysAllowAuthorizationService(禁用授权)
ABP 支持通过 AddAlwaysAllowAuthorization() 注册服务,用于集成测试时绕过授权。
注册 AlwaysAllowAuthorizationService:
using Volo.Abp.Authorization;
public override void ConfigureServices(ServiceConfigurationContext context)
{
// 仅在测试环境中启用
if (context.Services.IsTesting())
{
context.Services.AddAlwaysAllowAuthorization();
}
}
使用场景:
- 集成测试:避免权限检查干扰测试逻辑
- 开发调试:临时禁用授权以快速测试功能
- 数据迁移:某些迁移操作需要绕过权限
注意事项:
- ⚠️ 仅在测试或开发环境使用
- ⚠️ 生产环境必须禁用
- ⚠️ 不会影响
[AllowAnonymous]特性
3.3 基于资源的授权
ABP 支持针对具体资源(如单个出版商记录)的授权检查,通过实现 AuthorizationHandler<OperationAuthorizationRequirement, TResource>。
定义操作需求:
using Microsoft.AspNetCore.Authorization;
namespace Acme.BookStore.Authorization
{
public static class PublisherOperations
{
public static OperationAuthorizationRequirement Edit =
new OperationAuthorizationRequirement { Name = nameof(Edit) };
public static OperationAuthorizationRequirement Delete =
new OperationAuthorizationRequirement { Name = nameof(Delete) };
}
}
实现授权处理器:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Authorization;
using Volo.Abp.Users;
namespace Acme.BookStore.Authorization
{
public class PublisherAuthorizationHandler :
AuthorizationHandler<OperationAuthorizationRequirement, Publisher>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Publisher resource)
{
var userId = context.User.FindUserId();
if (userId == null)
{
context.Fail();
return Task.CompletedTask;
}
// 检查是否是创建者
if (resource.CreatorId == userId)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
// 检查是否有全局编辑权限
if (await AuthorizationService.IsGrantedAsync(BookStorePermissions.Publishers.Edit))
{
context.Succeed(requirement);
return Task.CompletedTask;
}
context.Fail();
return Task.CompletedTask;
}
}
}
注册授权处理器:
public override void ConfigureServices(ServiceConfigurationContext context)
{
var services = context.Services;
services.AddSingleton<IAuthorizationHandler<PublisherOperations.Edit, Publisher>, PublisherAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler<PublisherOperations.Delete, Publisher>, PublisherAuthorizationHandler>();
}
使用基于资源的授权:
public async Task<PublisherDto> UpdateAsync(Guid id, CreateUpdatePublisherDto input)
{
var publisher = await Repository.GetAsync(id);
// 使用基于资源的授权检查
await AuthorizationService.CheckAsync(PublisherOperations.Edit, publisher);
// 继续更新...
}
四、客户端权限检查细节
4.1 前端 JavaScript 权限检查
ABP 明确前端可通过 abp.auth.isGranted("权限名") 在 JS 中检查权限。
JavaScript API 使用:
// 检查单个权限
const canCreate = abp.auth.isGranted('BookStore.Publishers.Create');
if (canCreate) {
// 显示创建按钮
$('#createButton').show();
}
// 检查多个权限
const permissions = [
'BookStore.Publishers.Create',
'BookStore.Publishers.Edit',
'BookStore.Publishers.Delete'
];
const hasAnyPermission = permissions.some(p => abp.auth.isGranted(p));
if (hasAnyPermission) {
// 显示管理面板
$('#managementPanel').show();
}
// 检查所有权限
const hasAllPermissions = permissions.every(p => abp.auth.isGranted(p));
if (hasAllPermissions) {
// 显示完整功能
$('#fullFeatures').show();
}
React 前端示例:
import { usePermissions } from '@abp/ng.core';
function PublisherList() {
const { isGranted } = usePermissions();
return (
<div>
{isGranted('BookStore.Publishers.Create') && (
<Button>Create Publisher</Button>
)}
{isGranted('BookStore.Publishers.Edit') && (
<Button>Edit</Button>
)}
{isGranted('BookStore.Publishers.Delete') && (
<Button>Delete</Button>
)}
</div>
);
}
Angular 前端示例:
import { PermissionService } from '@abp/ng.core';
@Component({
selector: 'app-publisher-list',
template: `
<button *ngIf="permissionService.isGranted('BookStore.Publishers.Create')">
Create
</button>
<button *ngIf="permissionService.isGranted('BookStore.Publishers.Edit')">
Edit
</button>
`
})
export class PublisherListComponent {
constructor(public permissionService: PermissionService) {}
}
Vue 前端示例:
import { usePermission } from '@abp/vue';
export default {
setup() {
const { isGranted } = usePermission();
return {
canCreate: computed(() => isGranted('BookStore.Publishers.Create')),
canEdit: computed(() => isGranted('BookStore.Publishers.Edit')),
canDelete: computed(() => isGranted('BookStore.Publishers.Delete'))
};
}
};
权限变化监听:
// 监听权限变化
abp.event.on('abp.permissionsChanged', () => {
console.log('Permissions changed!');
// 重新检查权限并更新 UI
updateUI();
});
常见问题
Q1: 为什么构造函数中的权限配置"看起来没有被使用"?
A: 这些配置会被 CrudAppService 基类在内部使用。当您设置 CreatePolicyName = BookStorePermissions.Publishers.Create 后,基类会自动为 CreateAsync 方法添加授权检查。这是隐式使用,您看不到直接的调用。
验证方式:
// 您可以检查 ABP 源码中的 CrudAppService
// 它内部会调用类似这样的代码:
protected virtual async Task CheckCreatePolicyAsync()
{
if (CreatePolicyName != null)
{
await AuthorizationService.CheckAsync(CreatePolicyName);
}
}
Q2: [Authorize] 特性应该在接口定义还是实现类中添加?
A: 应该在实现类中添加。
❌ 不推荐:
[Authorize(BookStorePermissions.Publishers.Create)] // 不要在接口中添加
public interface IPublisherAppService
{
Task<PublisherDto> CreateAsync(CreateUpdatePublisherDto input);
}
✅ 推荐:
public interface IPublisherAppService
{
Task<PublisherDto> CreateAsync(CreateUpdatePublisherDto input);
}
[Authorize(BookStorePermissions.Publishers.Create)] // 在实现类中添加
public class PublisherAppService : IPublisherAppService
{
// ...
}
Q3: 如何实现"用户只能编辑自己创建的记录"?
A: 使用基于资源的授权或 AuthorizationService 检查:
public async Task<PublisherDto> UpdateAsync(Guid id, CreateUpdatePublisherDto input)
{
var publisher = await Repository.GetAsync(id);
// 方式1: 检查是否是创建者
if (publisher.CreatorId != CurrentUser.Id)
{
// 检查是否有编辑所有记录的权限
await AuthorizationService.CheckAsync(
BookStorePermissions.Publishers.Edit);
}
// 继续更新...
}
或者使用基于资源的授权(高级):
// 定义操作需求
public static class PublisherOperations
{
public static OperationAuthorizationRequirement Edit =
new OperationAuthorizationRequirement { Name = nameof(Edit) };
}
// 实现授权处理器
public class PublisherAuthorizationHandler :
AuthorizationHandler<OperationAuthorizationRequirement, Publisher>
{
protected override async Task HandleRequirementAsync(
AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Publisher resource)
{
// 检查是否是创建者
if (resource.CreatorId == context.User.FindUserId())
{
context.Succeed(requirement);
return;
}
// 检查是否有管理权限
if (await AuthorizationService.IsGrantedAsync(BookStorePermissions.Publishers.Edit))
{
context.Succeed(requirement);
return;
}
}
}
// 在应用服务中使用
public async Task<PublisherDto> UpdateAsync(Guid id, CreateUpdatePublisherDto input)
{
var publisher = await Repository.GetAsync(id);
// 基于资源的授权检查
await AuthorizationService.CheckAsync(publisher, PublisherOperations.Edit);
// 继续更新...
}
Q4: CheckAsync 和 IsGrantedAsync 有什么区别?
| 方法 | 返回值 | 无权限时的行为 | 适用场景 |
|---|---|---|---|
CheckAsync |
Task |
抛出 AbpAuthorizationException |
需要强制权限检查 |
IsGrantedAsync |
Task<bool> |
返回 false |
需要条件性逻辑 |
示例:
// CheckAsync: 无权限会抛出异常
public async Task<string> GetStatisticsAsync()
{
await AuthorizationService.CheckAsync(BookStorePermissions.Publishers.Default);
// 如果没有权限,不会执行到这里
return "...";
}
// IsGrantedAsync: 无权限返回 false
public async Task<PublisherDto> GetWithPermissionsAsync(Guid id)
{
var publisher = await Repository.GetAsync(id);
var dto = ObjectMapper.Map<Publisher, PublisherDto>(publisher);
// 不会抛出异常
dto.CanEdit = await AuthorizationService.IsGrantedAsync(
BookStorePermissions.Publishers.Edit);
return dto;
}
Q5: 如何调试权限问题?
方法1: 查看日志
[INF] Authorization: Checking permission: BookStore.Publishers.Create
[WRN] Authorization: Permission 'BookStore.Publishers.Create' was not granted for user: john.doe
方法2: 使用 Postman 测试
# 测试不同的用户/token
curl -X POST "https://localhost:44300/api/app/publisher" \
-H "Authorization: Bearer TOKEN_1"
curl -X POST "https://localhost:44300/api/app/publisher" \
-H "Authorization: Bearer TOKEN_2"
方法3: 在代码中添加日志
public async Task<PublisherDto> CreateAsync(CreateUpdatePublisherDto input)
{
Logger.LogInformation($"用户 {CurrentUser.UserName} 尝试创建出版商");
var hasPermission = await AuthorizationService.IsGrantedAsync(
BookStorePermissions.Publishers.Create);
Logger.LogInformation($"用户是否有创建权限: {hasPermission}");
// ...
}
Q6: 权限检查会影响性能吗?
A: 权限检查通常很快,因为:
- 权限信息缓存在内存中
- 用户的权限列表在登录时加载
- 使用高效的哈希表查找
优化建议:
// ❌ 不推荐:在循环中重复检查
public async Task ProcessPublishersAsync(List<Guid> ids)
{
foreach (var id in ids)
{
// 每次循环都检查权限(不必要)
await AuthorizationService.CheckAsync(BookStorePermissions.Publishers.Edit);
// ...
}
}
// ✅ 推荐:在循环外检查一次
public async Task ProcessPublishersAsync(List<Guid> ids)
{
// 只检查一次
await AuthorizationService.CheckAsync(BookStorePermissions.Publishers.Edit);
foreach (var id in ids)
{
// 不需要重复检查
// ...
}
}
权限检查流程图
用户请求 API 方法
↓
方法被调用
↓
┌─────────────────┐
│ 方式选择 │
└─────────────────┘
↓
┌────────┴────────┐
│ │
隐式方式 显式方式
(CrudAppService) ([Authorize] 或 AuthorizationService)
│ │
↓ ↓
检查属性值 检查特性或手动调用
│ │
└────────┬────────┘
↓
检查用户是否有权限
↓
┌─────┴─────┐
│ │
有权限 无权限
│ │
↓ ↓
执行方法 返回 403 Forbidden
│
↓
返回结果
📚 相关资源
✅ 总结
通过本指南,您已经学会了:
- ✅ 如何定义和注册权限
- ✅ 权限的三种使用方式及其适用场景
- ✅ 如何选择合适的授权方式
- ✅ 完整的代码实现示例
- ✅ 如何测试和调试权限问题
- ✅ 最佳实践和常见问题解答
关键要点:
- 隐式使用 适合标准 CRUD 操作
- [Authorize] 特性 适合自定义 API 方法
- AuthorizationService 最灵活,适合复杂场景
- 根据实际需求选择合适的方式
- 为权限添加本地化,提升用户体验
现在您的Publishers模块已经拥有完整的权限控制!🎉
文档版本: v1.0
最后更新: 2026-01-09
适用版本: ABP v8.1

浙公网安备 33010602011771号