项目
版本

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

UI
Database

Web应用程序开发教程 - 第五章: 授权

关于本教程

在本系列教程中, 你将构建一个名为 Acme.BookStore 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的:

  • MongoDB 做为ORM提供程序.
  • Blazor WebAssembly 做为UI框架.

本教程分为以下部分:

下载源码

本教程根据你的UI数据库偏好有多个版本,我们准备了几种可供下载的源码组合:

如果你在Windows中遇到 "文件名太长" 或 "解压错误", 很可能与Windows最大文件路径限制有关. Windows文件路径的最大长度为250字符. 为了解决这个问题,参阅 在Windows 10中启用长路径.

如果你遇到与Git相关的长路径错误, 尝试使用下面的命令在Windows中启用长路径. 参阅 https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path git config --system core.longpaths true

权限

ABP框架提供了一个基于ASP.NET Core授权基础架构授权系统. 基于标准授权基础架构的一个主要功能是添加了 权限系统, 这个系统允许定义权限并且根据角色, 用户或客户端启用/禁用权限.

权限名称

权限必须有唯一的名称 (一个 字符串). 最好的方法是把它定义为一个 常量, 这样我们就可以重用这个权限名称了.

打开 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"));

            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": "Book Store",
"Permission:Books": "Book Management",
"Permission:Books.Create": "Creating new books",
"Permission:Books.Edit": "Editing the books",
"Permission:Books.Delete": "Deleting the books"

本地化键名可以是任意的, 并没有强制的规则. 但我们推荐上面使用的约定. 简体中文翻译请打开zh-Hans.json文件 ,并将"Texts"对象中对应的值替换为中文.

权限管理界面

完成权限定义后, 可以在权限管理模态窗口看到它们.

管理 -> Identity -> 角色 页面, 选择admin角色的 权限 操作, 打开权限管理模态窗口:

bookstore-permissions-ui

授予你希望的权限并保存.

提示: 如果运行 Acme.BookStore.DbMigrator 应用程序, 新权限会被自动授予admin.

授权

现在, 你可以使用权限授权图书管理.

应用层 和 HTTP API

打开 the 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, //The Book entity
            BookDto, //Used to show books
            Guid, //Primary key of the book entity
            PagedAndSortedResultRequestDto, //Used for paging/sorting
            CreateUpdateBookDto>, //Used to create/update a book
        IBookAppService //implement the 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 controllers).

在稍后开发作者管理功能时, 你将会看到声明式授权, 使用 [Authorize(...)] 特性.

Razor验证组件

打开 Acme.BookStore.Blazor 项目中的 /Pages/Books.razor 文件, 在 @page 指令和命名空间引入(@using 行)后添加 Authorize 特性, 如下所示:

@page "/books"
@attribute [Authorize(BookStorePermissions.Books.Default)]
@using Acme.BookStore.Permissions
@using Microsoft.AspNetCore.Authorization
...

添加这个特性阻止未登录用户或未授权用户访问这个页面. 用户重试后, 会被重定向到登录页面.

显示/隐藏操作

图书管理页面上的每一种图书都有 新建 按钮和 编辑, 删除 操作. 如果用户没有相关权限, 这些按钮/操作应该被隐藏.

基类 AbpCrudPageBase 已经具有这些操作需要的功能.

设置策略 (权限) 名称

加入以下代码到 Books.razor 文件结尾:

@code
{
    public Books() // Constructor
    {
        CreatePolicyName = BookStorePermissions.Books.Create;
        UpdatePolicyName = BookStorePermissions.Books.Edit;
        DeletePolicyName = BookStorePermissions.Books.Delete;
    }
}

基类 AbpCrudPageBase 自动检查相关操作的权限. 如果需要手动检查, 它也定义了相应的属性.

  • HasCreatePermission: True, 如果用户具有新建实体的权限.
  • HasUpdatePermission: True, 如果用户具有编辑/更新实体的权限.
  • HasDeletePermission: True, 如果用户具有删除实体的权限.

Blazor 提示: 当添加少量代码到 @code 是没有问题的. 当添加的代码变长时, 建议使用代码后置方法以便于维护. 我们将在作者部分使用这个方法.

隐藏新建图书按钮

检查 新建图书 按钮权限:

@if (HasCreatePermission)
{
    <Button Color="Color.Primary"
            Clicked="OpenCreateModalAsync">@L["NewBook"]</Button>
}

隐藏编辑/删除操作

EntityAction 组件定义了 Visible 属性 (参数) 以条件显示操作.

更新 EntityActions 部分:

<EntityActions TItem="BookDto" EntityActionsColumn="@EntityActionsColumn">
    <EntityAction TItem="BookDto"
                  Text="@L["Edit"]"
                  Visible=HasUpdatePermission
                  Clicked="() => OpenEditModalAsync(context)" />
    <EntityAction TItem="BookDto"
                  Text="@L["Delete"]"
                  Visible=HasDeletePermission
                  Clicked="() => DeleteEntityAsync(context)"
                  ConfirmationMessage="()=>GetDeleteConfirmationMessage(context)" />
</EntityActions>

关于权限缓存

你可以运行和测试权限. 从admin角色中移除一个图书相关权限, 观察到相关按钮/操作从UI上消失.

在客户端, ABP框架缓存当前用户的权限 . 所以, 当你修改了你的权限, 你需要手工 刷新页面. 如果不刷新并试图使用被禁的操作, 你会从服务器收到一个HTTP 403 (forbidden) 响应.

修改角色或用户的权限在服务端立即生效. 所以, 缓存系统不会导致安全问题.

菜单项

即使我们在图书管理页面的所有层都控制了权限, 应用程序的主菜单依然会显示. 我们应该隐藏用户没有权限的菜单项.

打开 Acme.BookStore.Blazor 项目中的 BookStoreMenuContributor 类, 找到以下代码:

context.Menu.AddItem(
    new ApplicationMenuItem(
        "BooksStore",
        l["Menu:BookStore"],
        icon: "fa fa-book"
    ).AddItem(
        new ApplicationMenuItem(
            "BooksStore.Books",
            l["Menu:Books"],
            url: "/books"
        )
    )
);

替换为以下代码:

var bookStoreMenu = new ApplicationMenuItem(
    "BooksStore",
    l["Menu:BookStore"],
    icon: "fa fa-book"
);

context.Menu.AddItem(bookStoreMenu);

//CHECK the PERMISSION
if (await context.IsGrantedAsync(BookStorePermissions.Books.Default))
{
    bookStoreMenu.AddItem(new ApplicationMenuItem(
        "BooksStore.Books",
        l["Menu:Books"],
        url: "/books"
    ));
}

你需要为 ConfigureMenuAsync 方法加入 async 关键字并重新整理返回值. 最终的 ConfigureMainMenuAsync 方法如下:

private async Task ConfigureMainMenuAsync(MenuConfigurationContext context)
{
    var l = context.GetLocalizer<BookStoreResource>();

    context.Menu.Items.Insert(
        0,
        new ApplicationMenuItem(
            "BookStore.Home",
            l["Menu:Home"],
            "/",
            icon: "fas fa-home"
        )
    );

    var bookStoreMenu = new ApplicationMenuItem(
        "BooksStore",
        l["Menu:BookStore"],
        icon: "fa fa-book"
    );

    context.Menu.AddItem(bookStoreMenu);

    //CHECK the PERMISSION
    if (await context.IsGrantedAsync(BookStorePermissions.Books.Default))
    {
        bookStoreMenu.AddItem(new ApplicationMenuItem(
            "BooksStore.Books",
            l["Menu:Books"],
            url: "/books"
        ));
    }
}

下一章

查看本教程的下一章.

在本文档中