项目

本文档有多个版本。请选择最适合您的选项。

UI
Database

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 角色的 权限 操作以打开权限管理模态框:

bookstore-permissions-ui

授予你想要的权限并保存模态框。

提示:如果你运行 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);
});

现在,未经授权的用户将被重定向到登录页面

隐藏新建图书按钮

图书管理页面有一个新建图书按钮,如果当前用户没有图书创建权限,该按钮应该是不可见的。

bookstore-new-book-button-small

打开 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 端

图书管理页面中的图书表格每一行都有一个操作按钮。操作按钮包括编辑删除操作:

bookstore-edit-delete-actions

如果当前用户未被授予相关权限,我们应该隐藏某个操作。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) 扩展方法调用。


在本文档中