乐于分享
好东西不私藏

ABP 授权源码解读:PermissionDefinitionProvider 与权限检查链

ABP 授权源码解读:PermissionDefinitionProvider 与权限检查链

授权系统的三层结构

ABP 的权限不是扁平的字符串列表,而是三层结构:

// PermissionGroupDefinition.cs — 组,功能模块的顶级分类
context.AddGroup("BookStore", L("图书管理"));

// PermissionDefinition.cs — 权限,具体功能

group
.AddPermission("BookStore.Books", L("图书管理"));

// 子权限(Children)— 更细粒度的操作

books.AddChild("BookStore.Books.Create", L("创建"));
books.AddChild("BookStore.Books.Delete", L("删除"));

PermissionDefinition 的两个关键属性:

public class PermissionDefinition
{
    public
 string Name { get; }             // 权限唯一标识,如 "BookStore.Books"
    public
 PermissionDefinition? Parent { get; private set; } // 父权限
    public
 IReadOnlyList<PermissionDefinition> Children { get; } // 子权限

    // 子权限继承:如果用户没有 "BookStore.Books" 权限,

    // 即使勾选了 "BookStore.Books.Create" 也不应该生效

}

权限定义器的工作流程

public abstract class PermissionDefinitionProvider : IPermissionDefinitionProvider, ITransientDependency
{
public virtual void PreDefine(IPermissionDefinitionContext context)
 { }
public abstract void Define(IPermissionDefinitionContext context)
;   // 必须实现
public virtual void PostDefine(IPermissionDefinitionContext context)
 { }
}

三个方法按 PreDefine → Define → PostDefine 顺序调用,类似模块生命周期的三段式。PreDefine 和 PostDefine 用于模块间的权限定义覆盖。

权限检查链

当调用 [Authorize("BookStore.Books.Create")] 时,完整链如下:

1. ASP.NET Core AuthorizeFilter(MVC 过滤器)
   ↓ 检查用户是否已认证
2. Castle DynamicProxy AbpAuthorizationInterceptor
   ↓ 检查 [Authorize] 属性
3. IAuthorizationService.CheckAsync("BookStore.Books.Create")
   ↓
4. IPermissionChecker.IsGrantedAsync("BookStore.Books.Create")
   ↓
5. PermissionStore 从数据库查询用户/角色是否有该权限
   ↓
6. 返回 true → 继续执行
   返回 false → 抛出 AbpAuthorizationException → HTTP 403

IPermissionChecker 的层次

public interface IPermissionChecker
{
    // 检查单个权限

    Task<bool> IsGrantedAsync(string name)
;

    // 批量检查多个权限(一次数据库查询,减少 N+1)

    Task<MultiplePermissionGrantResult> IsGrantedAsync(string[] names)
;
}

IsGrantedAsync(string[]) 用于页面渲染时批量检查权限,避免多次数据库查询。

ICurrentUser 和 IAuthorizationService 的职责分离

// ICurrentUser — 提供用户身份信息(适合 UI 层判断"当前是谁")
public
 interface ICurrentUser
{
    bool
 IsAuthenticated { get; }
    Guid? Id { get; }
    string
[] Roles { get; }
bool IsInRole(string roleName)
;
}

// IAuthorizationService — 提供权限检查(适合业务层"能做什么")

// 通过 ApplicationService 基类可直接使用

await
 AuthorizationService.CheckAsync("BookStore.Books");
await
 AuthorizationService.IsGrantedAsync("BookStore.Books");

实战:三种授权方式的选用

// 场景 1:接口级别的权限控制 → [Authorize] 最合适
[Authorize("BookStore.Books.Create")]
public async Task<BookDto> CreateAsync(CreateBookDto input)
 { }

// 场景 2:方法内部条件性判断 → AuthorizationService

public async Task DeleteAsync(Guid id)

{
    if
 (CurrentUser.Id == book.CreatorId)
    {
        // 创建者可以删除自己的书

        await
 _bookRepo.DeleteAsync(id);
    }
    else
 if (await AuthorizationService.IsGrantedAsync("BookStore.Books.Delete"))
    {
        // 有权限的管理员也可以删除

        await
 _bookRepo.DeleteAsync(id);
    }
}

// 场景 3:只在 UI 上隐藏按钮 → ICurrentUser 或直接查权限

if
 (await AuthorizationService.IsGrantedAsync("BookStore.Books.Delete"))
{
    // 显示"删除"按钮

}