Web 应用程序开发教程 - 第 5 部分:授权
权限
ABP 提供了一个基于 ASP.NET Core 授权基础架构 的授权系统。在标准授权基础架构之上添加的一个主要功能是权限系统,它允许定义权限,并按角色、用户或客户端启用/禁用。
权限名称
一个权限必须有一个唯一的名称(一个 string)。最好的方式是将其定义为 const,以便我们可以重复使用该权限名称。
打开 Acme.BookStore.Application.Contracts 项目中的 BookStorePermissions 类(位于 Permissions 文件夹内),并添加新的权限名称:
namespace Acme.BookStore.Permissions;
public static class BookStorePermissions
{
public const string GroupName = "BookStore";
// 其他权限...
// 其他权限...
// *** 添加了一个新的嵌套类 ***
public static class Books
{
public const string Default = GroupName + ".Books";
public const string Create = Default + ".Create";
public const string Edit = Default + ".Edit";
public const string Delete = Default + ".Delete";
}
}
这是定义权限名称的一种分层方式。例如,“创建图书”的权限名称被定义为 BookStore.Books.Create。ABP 并不强制要求你采用某种结构,但我们发现这种方式很有用。
权限定义
在使用权限之前,你应该先定义它们。
打开 Acme.BookStore.Application.Contracts 项目中的 BookStorePermissionDefinitionProvider 类(位于 Permissions 文件夹内),并按如下所示更改内容:
using Acme.BookStore.Localization;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Localization;
namespace Acme.BookStore.Permissions;
public class BookStorePermissionDefinitionProvider : PermissionDefinitionProvider
{
public override void Define(IPermissionDefinitionContext context)
{
var bookStoreGroup = context.AddGroup(BookStorePermissions.GroupName, L("Permission:BookStore"));
// 仪表板权限
bookStoreGroup.AddPermission(BookStorePermissions.Dashboard.Host, L("Permission:Dashboard"), MultiTenancySides.Host);
bookStoreGroup.AddPermission(BookStorePermissions.Dashboard.Tenant, L("Permission:Dashboard"), MultiTenancySides.Tenant);
// 图书权限
var booksPermission = bookStoreGroup.AddPermission(BookStorePermissions.Books.Default, L("Permission:Books"));
booksPermission.AddChild(BookStorePermissions.Books.Create, L("Permission:Books.Create"));
booksPermission.AddChild(BookStorePermissions.Books.Edit, L("Permission:Books.Edit"));
booksPermission.AddChild(BookStorePermissions.Books.Delete, L("Permission:Books.Delete"));
}
private static LocalizableString L(string name)
{
return LocalizableString.Create<BookStoreResource>(name);
}
}
这个类定义了一个权限组(用于在 UI 上分组权限,稍后会看到)和该组内的 4 个权限。此外,创建、编辑和删除是 BookStorePermissions.Books.Default 权限的子权限。一个子权限只有在父权限被选中时才能被选中。
最后,编辑本地化文件(Acme.BookStore.Domain.Shared 项目的 Localization/BookStore 文件夹下的 en.json),以定义上面使用的本地化键:
"Permission:BookStore": "书店",
"Permission:Books": "图书管理",
"Permission:Books.Create": "创建新图书",
"Permission:Books.Edit": "编辑图书",
"Permission:Books.Delete": "删除图书"
本地化键的名称是任意的,没有强制规则。但我们更喜欢上面使用的约定。
权限管理 UI
一旦定义了权限,你就可以在权限管理模态框中看到它们。
进入 管理 -> 身份标识 -> 角色 页面,选择 admin 角色的 权限 操作以打开权限管理模态框:
授予你想要的权限并保存模态框。
提示:如果你运行
Acme.BookStore.DbMigrator应用程序,新权限会自动授予 admin 角色。
授权
现在,你可以使用这些权限来授权图书管理操作。
应用层 & HTTP API
打开 BookAppService 类,并将策略名称设置为上面定义的权限名称:
using System;
using Acme.BookStore.Permissions;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore.Books;
public class BookAppService :
CrudAppService<
Book, // Book 实体
BookDto, // 用于显示图书
Guid, // Book 实体的主键
PagedAndSortedResultRequestDto, // 用于分页/排序
CreateUpdateBookDto>, // 用于创建/更新图书
IBookAppService // 实现 IBookAppService
{
public BookAppService(IRepository<Book, Guid> repository)
: base(repository)
{
GetPolicyName = BookStorePermissions.Books.Default;
GetListPolicyName = BookStorePermissions.Books.Default;
CreatePolicyName = BookStorePermissions.Books.Create;
UpdatePolicyName = BookStorePermissions.Books.Edit;
DeletePolicyName = BookStorePermissions.Books.Delete;
}
}
已将代码添加到构造函数中。基类 CrudAppService 会在 CRUD 操作中自动使用这些权限。这使得应用程序服务是安全的,同时也使得 HTTP API 是安全的,因为如前所述,此服务会自动用作 HTTP API(参见自动 API 控制器)。
你将在开发作者管理功能时看到使用
[Authorize(...)]属性的声明式授权。
Razor Page
虽然保护 HTTP API 和应用程序服务可以防止未经授权的用户使用服务,但他们仍然可以导航到图书管理页面。尽管他们会在页面首次向服务器发出 AJAX 调用时收到授权异常,但为了更好的用户体验和安全性,我们也应该对页面进行授权。
打开 BookStoreWebModule,并在 ConfigureServices 方法内添加以下代码块:
Configure<RazorPagesOptions>(options =>
{
options.Conventions.AuthorizePage("/Books/Index", BookStorePermissions.Books.Default);
options.Conventions.AuthorizePage("/Books/CreateModal", BookStorePermissions.Books.Create);
options.Conventions.AuthorizePage("/Books/EditModal", BookStorePermissions.Books.Edit);
});
现在,未经授权的用户将被重定向到登录页面。
隐藏新建图书按钮
图书管理页面有一个新建图书按钮,如果当前用户没有图书创建权限,该按钮应该是不可见的。
打开 Pages/Books/Index.cshtml 文件,并按如下所示更改内容:
@page
@using Acme.BookStore.Localization
@using Acme.BookStore.Permissions
@using Acme.BookStore.Web.Pages.Books
@using Microsoft.AspNetCore.Authorization
@using Microsoft.Extensions.Localization
@model IndexModel
@inject IStringLocalizer<BookStoreResource> L
@inject IAuthorizationService AuthorizationService
@section scripts
{
<abp-script src="/Pages/Books/Index.js"/>
}
<abp-card>
<abp-card-header>
<abp-row>
<abp-column size-md="_6">
<abp-card-title>@L["Books"]</abp-card-title>
</abp-column>
<abp-column size-md="_6" class="text-end">
@if (await AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Create))
{
<abp-button id="NewBookButton"
text="@L["NewBook"].Value"
icon="plus"
button-type="Primary"/>
}
</abp-column>
</abp-row>
</abp-card-header>
<abp-card-body>
<abp-table striped-rows="true" id="BooksTable"></abp-table>
</abp-card-body>
</abp-card>
- 添加了
@inject IAuthorizationService AuthorizationService以访问授权服务。 - 使用了
@if (await AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Create))来检查图书创建权限,从而有条件地渲染新建图书按钮。
JavaScript 端
图书管理页面中的图书表格每一行都有一个操作按钮。操作按钮包括编辑和删除操作:
如果当前用户未被授予相关权限,我们应该隐藏某个操作。DataTables 的行操作有一个 visible 选项,可以设置为 false 来隐藏操作项。
打开 Acme.BookStore.Web 项目中的 Pages/Books/Index.js 文件,并按如下所示为 Edit 操作添加 visible 选项:
{
text: l('Edit'),
visible: abp.auth.isGranted('BookStore.Books.Edit'), // 检查权限
action: function (data) {
editModal.open({ id: data.record.id });
}
}
对 Delete 操作执行相同的操作:
visible: abp.auth.isGranted('BookStore.Books.Delete')
abp.auth.isGranted(...)用于检查之前定义的权限。visible也可以是一个返回bool的函数,如果该值稍后需要根据某些条件计算。
菜单项
即使我们已经保护了图书管理页面的所有层,它仍然显示在应用程序的主菜单上。如果当前用户没有权限,我们应该隐藏该菜单项。
打开 BookStoreMenuContributor 类,找到下面的代码块:
context.Menu.AddItem(
new ApplicationMenuItem(
"BooksStore",
l["Menu:BookStore"],
icon: "fa fa-book"
).AddItem(
new ApplicationMenuItem(
"BooksStore.Books",
l["Menu:Books"],
url: "/Books"
)
)
);
并用以下代码块替换它:
context.Menu.AddItem(
new ApplicationMenuItem(
"BooksStore",
l["Menu:BookStore"],
icon: "fa fa-book"
).AddItem(
new ApplicationMenuItem(
"BooksStore.Books",
l["Menu:Books"],
url: "/Books"
).RequirePermissions(BookStorePermissions.Books.Default) // 检查权限!
)
);
我们只为内部菜单项添加了 .RequirePermissions(BookStorePermissions.Books.Default) 扩展方法调用。
抠丁客





