项目

授权机制

授权用于验证用户是否被允许在应用程序中执行特定操作。

ABP框架通过引入权限作为自动化 策略 ,并支持在 应用服务 中使用授权系统,对 ASP.NET Core 授权功能 进行了扩展。

因此,所有ASP.NET Core授权特性及文档在基于ABP的应用程序中均适用。本文重点介绍在ASP.NET Core授权基础上新增的功能。

授权特性(Authorize Attribute)

ASP.NET Core定义了 Authorize 特性,可用于控制器动作、控制器或页面。ABP允许您同样在 应用服务 中使用此特性。

示例:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Services;

namespace Acme.BookStore
{
    [Authorize]
    public class AuthorAppService : ApplicationService, IAuthorAppService
    {
        public Task<List<AuthorDto>> GetListAsync()
        {
            ...
        }

        [AllowAnonymous]
        public Task<AuthorDto> GetAsync(Guid id)
        {
            ...
        }

        [Authorize("BookStore_Author_Create")]
        public Task CreateAsync(CreateAuthorDto input)
        {
            ...
        }
    }
}
  • Authorize特性要求用户必须登录才能使用AuthorAppService中的方法。因此,GetListAsync方法仅对已认证用户可用。
  • AllowAnonymous特性禁用认证要求。因此,GetAsync方法对所有人开放,包括未授权用户。
  • [Authorize("BookStore_Author_Create")]定义了一个策略(参见 基于策略的授权 ),用于验证当前用户权限。

"BookStore_Author_Create"是一个任意策略名称。若声明此类特性,ASP.NET Core授权系统要求预先定义对应策略。

当然,您可以按照ASP.NET Core文档实现自定义策略。但对于简单的真假条件判断(如是否对用户授予策略),ABP定义了权限系统,下文将详细说明。

权限系统

权限是一种简单的策略,可针对特定用户、角色或客户端进行授予或禁止。

定义权限

要定义权限,需创建继承自PermissionDefinitionProvider的类,如下所示:

using Volo.Abp.Authorization.Permissions;

namespace Acme.BookStore.Permissions
{
    public class BookStorePermissionDefinitionProvider : PermissionDefinitionProvider
    {
        public override void Define(IPermissionDefinitionContext context)
        {
            var myGroup = context.AddGroup("BookStore");

            myGroup.AddPermission("BookStore_Author_Create");
        }
    }
}

ABP会自动发现此类,无需额外配置!

通常您应在 应用程序Application.Contracts 项目中定义此类。启动模板已包含名为YourProjectNamePermissionDefinitionProvider的空类供您使用。

Define方法中,首先需添加权限组或获取现有组,然后向该组添加权限

定义权限后,它可在ASP.NET Core授权系统中作为策略名称使用,并在UI中可见。参见角色权限对话框:

authorization-new-permission-ui

  • "BookStore"组在左侧显示为新标签页。
  • 右侧的"BookStore_Author_Create"为权限名称。可针对角色授予或禁止该权限。

保存对话框后,设置将存储至数据库并在授权系统中使用。

上述界面在安装身份认证模块后可用,该模块主要用于用户与角色管理。启动模板已预装身份认证模块。

本地化权限名称

"BookStore_Author_Create"并非UI友好的权限名称。幸运的是,AddPermissionAddGroup方法的第二个参数可接受LocalizableString

var myGroup = context.AddGroup(
    "BookStore",
    LocalizableString.Create<BookStoreResource>("BookStore")
);

myGroup.AddPermission(
    "BookStore_Author_Create",
    LocalizableString.Create<BookStoreResource>("Permission:BookStore_Author_Create")
);

随后您可在本地化文件中为"BookStore"和"Permission:BookStore_Author_Create"键定义文本:

"BookStore": "图书商店",
"Permission:BookStore_Author_Create": "创建新作者"

更多信息请参阅 本地化系统文档

本地化后的UI如下所示:

authorization-new-permission-ui-localized

多租户支持

ABP以一等公民身份支持 多租户 。定义新权限时可设置多租户选项,其可选以下三个值之一:

  • Host:权限仅主机端可用。
  • Tenant:权限仅租户端可用。
  • Both(默认):权限同时适用于租户和主机端。

若应用程序非多租户,可忽略此选项。

通过AddPermission方法的第三个参数设置多租户选项:

myGroup.AddPermission(
    "BookStore_Author_Create",
    LocalizableString.Create<BookStoreResource>("Permission:BookStore_Author_Create"),
    multiTenancySide: MultiTenancySides.Tenant //设置多租户端!
);

启用/禁用权限

权限默认启用。可禁用权限,被禁用的权限将对所有人禁止。仍可检查该权限,但结果始终为禁止。

示例定义:

myGroup.AddPermission("Author_Management", isEnabled: false);

通常无需定义禁用权限(除非临时禁用应用程序功能)。但您可能希望禁用依赖模块中定义的权限,从而禁用相关应用功能。参见下文"修改依赖模块权限定义"章节的示例用法。

注意:检查未定义权限将抛出异常,而检查禁用权限仅返回禁止(false)。

子权限

权限可包含子权限。这在创建分层权限树时特别有用,父权限授予后,子权限才可用。

示例定义:

var authorManagement = myGroup.AddPermission("Author_Management");
authorManagement.AddChild("Author_Management_Create_Books");
authorManagement.AddChild("Author_Management_Edit_Books");
authorManagement.AddChild("Author_Management_Delete_Books");

UI显示结果如下(建议为应用程序本地化权限):

authorization-new-permission-ui-hierarcy

示例代码中,假设拥有"Author_Management"权限的角色/用户可能具备额外权限。随后,检查权限的典型应用服务可定义如下:

[Authorize("Author_Management")]
public class AuthorAppService : ApplicationService, IAuthorAppService
{
    public Task<List<AuthorDto>> GetListAsync()
    {
        ...
    }

    public Task<AuthorDto> GetAsync(Guid id)
    {
        ...
    }

    [Authorize("Author_Management_Create_Books")]
    public Task CreateAsync(CreateAuthorDto input)
    {
        ...
    }

    [Authorize("Author_Management_Edit_Books")]
    public Task UpdateAsync(CreateAuthorDto input)
    {
        ...
    }

    [Authorize("Author_Management_Delete_Books")]
    public Task DeleteAsync(CreateAuthorDto input)
    {
        ...
    }
}
  • 若用户被授予Author_Management权限,则GetListAsyncGetAsync方法可用。
  • 其他方法需要额外权限。

通过自定义策略覆盖权限

若您定义并向ASP.NET Core授权系统注册与权限同名的策略,您的策略将覆盖现有权限。这是扩展应用程序中所用预构建模块授权的强大方式。

参阅 基于策略的授权 文档了解如何定义自定义策略。

修改依赖模块的权限定义

继承自PermissionDefinitionProvider的类(如上例)也可获取现有权限定义(由依赖 模块 定义)并修改其定义。

示例:

context
    .GetPermissionOrNull(IdentityPermissions.Roles.Delete)
    .IsEnabled = false;

在权限定义提供程序中编写此代码,它将找到 身份认证模块 的"角色删除"权限并禁用它,从而使应用程序中无人能删除角色。

提示:最好检查GetPermissionOrNull方法的返回值,因为若给定权限未定义,它可能返回null。

条件依赖权限

您可能希望基于条件禁用权限。禁用权限在UI中不可见,且检查时始终返回禁止。权限定义有两个内置条件依赖:

  • 功能 被禁用,权限可自动禁用。
  • 全局功能 被禁用,权限可自动禁用。

此外,您可创建自定义扩展。

依赖功能

在权限定义上使用 RequireFeatures 扩展方法,使权限仅在给定功能启用时可用:

myGroup.AddPermission("Book_Creation")
    .RequireFeatures("BookManagement");

依赖全局功能

在权限定义上使用 RequireGlobalFeatures 扩展方法,使权限仅在给定功能启用时可用:

myGroup.AddPermission("Book_Creation")
    .RequireGlobalFeatures("BookManagement");

创建自定义权限依赖

PermissionDefinition 支持状态检查,请参阅 简单状态检查器文档

IAuthorizationService

ASP.NET Core 提供了 IAuthorizationService 用于检查授权。注入后,可在代码中条件性控制授权。

示例:

public async Task CreateAsync(CreateAuthorDto input)
{
    var result = await AuthorizationService
        .AuthorizeAsync("Author_Management_Create_Books");
    if (result.Succeeded == false)
    {
        //抛出异常
        throw new AbpAuthorizationException("...");
    }

    //继续正常流程...
}

当您继承ABP的ApplicationService基类时,AuthorizationService作为属性可用。由于在应用服务中广泛使用,ApplicationService已预先注入该服务。否则,您可直接 注入 到类中。

由于这是典型代码块,ABP提供了扩展方法以简化操作。

示例:

public async Task CreateAsync(CreateAuthorDto input)
{
    await AuthorizationService.CheckAsync("Author_Management_Create_Books");

    //继续正常流程...
}

若当前用户/客户端未被授予给定权限,CheckAsync 扩展方法将抛出 AbpAuthorizationException 异常。此外还有返回 truefalseIsGrantedAsync 扩展方法。

IAuthorizationServiceAuthorizeAsync 方法有若干重载,这些在 ASP.NET Core 授权文档 中有说明。

提示:尽可能使用Authorize特性,因其为声明式且简单。若需条件性检查权限并根据检查结果执行业务代码,请使用IAuthorizationService

JavaScript中的权限检查

参阅以下文档了解如何在客户端复用授权系统:

权限管理

权限管理通常由管理员用户通过权限管理模态框完成:

authorization-new-permission-ui-localized

若需通过代码管理权限,注入 IPermissionManager 并按如下方式使用:

public class MyService : ITransientDependency
{
    private readonly IPermissionManager _permissionManager;

    public MyService(IPermissionManager permissionManager)
    {
        _permissionManager = permissionManager;
    }

    public async Task GrantPermissionForUserAsync(Guid userId, string permissionName)
    {
        await _permissionManager.SetForUserAsync(userId, permissionName, true);
    }

    public async Task ProhibitPermissionForUserAsync(Guid userId, string permissionName)
    {
        await _permissionManager.SetForUserAsync(userId, permissionName, false);
    }
}

SetForUserAsync设置用户权限的值(true/false)。还有更多扩展方法如SetForRoleAsyncSetForClientAsync

IPermissionManager由权限管理模块定义。更多信息请参阅 权限管理模块文档

高级主题

权限值提供程序

权限检查系统可扩展。任何继承自 PermissionValueProvider(或实现IPermissionValueProvider)的类均可参与权限检查。有三个预定义的值提供程序:

  • UserPermissionValueProvider检查当前用户是否被授予给定权限。它从当前声明中获取用户ID。用户声明名称由AbpClaimTypes.UserId静态属性定义。
  • RolePermissionValueProvider检查当前用户任何角色是否被授予给定权限。它从当前声明中获取角色名称。角色声明名称由AbpClaimTypes.Role静态属性定义。
  • ClientPermissionValueProvider检查当前客户端是否被授予给定权限。这在机器对机器交互中特别有用,因无当前用户。它从当前声明中获取客户端ID。客户端声明名称由AbpClaimTypes.ClientId静态属性定义。

您可通过定义自己的权限值提供程序扩展权限检查系统。

示例:

public class SystemAdminPermissionValueProvider : PermissionValueProvider
{
    public SystemAdminPermissionValueProvider(IPermissionStore permissionStore)
        : base(permissionStore)
    {
    }

    public override string Name => "SystemAdmin";

    public async override Task<PermissionGrantResult>
           CheckAsync(PermissionValueCheckContext context)
    {
        if (context.Principal?.FindFirst("User_Type")?.Value == "SystemAdmin")
        {
            return PermissionGrantResult.Granted;
        }

        return PermissionGrantResult.Undefined;
    }
}

此提供程序将所有权授予给拥有User_Type声明且值为SystemAdmin的用户。权限值提供程序中通常使用当前声明和IPermissionStore

权限值提供程序应从CheckAsync方法返回以下值之一:

  • PermissionGrantResult.Granted授予用户权限。若任何提供程序返回Granted,且无其他提供程序返回Prohibited,则结果为Granted
  • PermissionGrantResult.Prohibited禁止用户权限。若任何提供程序返回Prohibited,则结果始终为Prohibited,无论其他提供程序返回何值。
  • PermissionGrantResult.Undefined表示此值提供程序无法决定权限值。返回此值以让其他提供程序检查权限。

定义提供程序后,应将其添加到AbpPermissionOptions中:

Configure<AbpPermissionOptions>(options =>
{
    options.ValueProviders.Add<SystemAdminPermissionValueProvider>();
});

权限存储

IPermissionStore是唯一需要实现的接口,用于从持久化源(通常是数据库系统)读取权限值。权限管理模块实现了此接口,并预装在应用程序启动模板中。更多信息请参阅 权限管理模块文档

AlwaysAllowAuthorizationService

AlwaysAllowAuthorizationService是用于绕过授权服务的类。通常在集成测试中使用,以禁用授权系统。

使用IServiceCollection.AddAlwaysAllowAuthorization()扩展方法将AlwaysAllowAuthorizationService注册到 依赖注入 系统:

public override void ConfigureServices(ServiceConfigurationContext context)
{
    context.Services.AddAlwaysAllowAuthorization();
}

启动模板的集成测试中已实现此操作。

声明主体工厂

声明是认证与授权的重要元素。ABP使用IAbpClaimsPrincipalFactory服务在认证时创建声明。此服务设计为可扩展。若需向认证凭据添加自定义声明,可在应用程序中实现IAbpClaimsPrincipalContributor

示例:添加SocialSecurityNumber声明并获取:

public class SocialSecurityNumberClaimsPrincipalContributor : IAbpClaimsPrincipalContributor, ITransientDependency
{
    public async Task ContributeAsync(AbpClaimsPrincipalContributorContext context)
    {
        var identity = context.ClaimsPrincipal.Identities.FirstOrDefault();
        var userId = identity?.FindUserId();
        if (userId.HasValue)
        {
            var userService = context.ServiceProvider.GetRequiredService<IUserService>(); //您的自定义服务
            var socialSecurityNumber = await userService.GetSocialSecurityNumberAsync(userId.Value);
            if (socialSecurityNumber != null)
            {
                identity.AddClaim(new Claim("SocialSecurityNumber", socialSecurityNumber));
            }
        }
    }
}


public static class CurrentUserExtensions
{
    public static string GetSocialSecurityNumber(this ICurrentUser currentUser)
    {
        return currentUser.FindClaimValue("SocialSecurityNumber");
    }
}

若使用 OpenIddict,请参阅 更新 Access Token 和 ID Token 中的声明

另请参阅

在本文档中