授权机制
授权用于验证用户是否被允许在应用程序中执行特定操作。
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中可见。参见角色权限对话框:
- "BookStore"组在左侧显示为新标签页。
- 右侧的"BookStore_Author_Create"为权限名称。可针对角色授予或禁止该权限。
保存对话框后,设置将存储至数据库并在授权系统中使用。
上述界面在安装身份认证模块后可用,该模块主要用于用户与角色管理。启动模板已预装身份认证模块。
本地化权限名称
"BookStore_Author_Create"并非UI友好的权限名称。幸运的是,AddPermission和AddGroup方法的第二个参数可接受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如下所示:
多租户支持
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显示结果如下(建议为应用程序本地化权限):
示例代码中,假设拥有"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权限,则GetListAsync和GetAsync方法可用。 - 其他方法需要额外权限。
通过自定义策略覆盖权限
若您定义并向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 异常。此外还有返回 true 或 false 的 IsGrantedAsync 扩展方法。
IAuthorizationService 的 AuthorizeAsync 方法有若干重载,这些在 ASP.NET Core 授权文档 中有说明。
提示:尽可能使用
Authorize特性,因其为声明式且简单。若需条件性检查权限并根据检查结果执行业务代码,请使用IAuthorizationService。
JavaScript中的权限检查
参阅以下文档了解如何在客户端复用授权系统:
权限管理
权限管理通常由管理员用户通过权限管理模态框完成:
若需通过代码管理权限,注入 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)。还有更多扩展方法如SetForRoleAsync和SetForClientAsync。
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 中的声明。
抠丁客





