zoukankan      html  css  js  c++  java
  • Web应用开发教程 Part 5: 授权

    Web应用开发教程 - Part 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";
            }
        }
    }
    

    This is a hierarchical way of defining permission names. For example, "create book" permission name was defined as BookStore.Books.Create. ABP doesn't force you to a structure, but we find this way useful.

    这是一种定义权限名称的划分层级方式。例如,新建书籍的权限名称被定义为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);
            }
        }
    }
    

    这个类定义了一个权限组(在用户界面上进行权限分组,下面会看到)和这个组内的4个权限。另外,创建编辑删除BookStorePermissions.Books.Default 权限的子权限。只有当父权限被选中时,子权限才能被选中。

    最后,编辑本地化文件(en.jsonAcme.BookStore.Domain.Shared项目的Localization/BookStore文件夹下)定义上面使用的本地化键值对。

    "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"
    

    本地化键的名称是随意的,没有强制的规则。但我们更喜欢上面的使用习惯。

    权限管理界面

    一旦你定义了权限,你可以在权限管理中看到它们。

    进入管理->身份->角色页面,点击管理员角色的权限按钮,打开权限管理:

    image

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

    提示: 如果你运行Acme.BookStore.DbMigrator 应用程序,新的权限会自动授予管理员角色。

    授权

    现在,你可以使用权限来授权书籍管理。

    应用层 & 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, //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,正如之前解释的那样(参考auto API controllers)。

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

    {{if UI == "MVC"}}

    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);
    });
    

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

    隐藏新建书籍按钮

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

    image

    打开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-right">
                    @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

    图书管理页面中的图书列表,每一行都有一个操作按钮。该行为按钮包含编辑删除动作。

    image

    如果当前用户没有授予相关权限,我们应该隐藏这个动作。数据表行操作有一个visible选项,可以设置为false来隐藏动作项。

    打开Acme.BookStore.Web项目中的Pages/Books/Index.js,为Edit动作添加一个visible选项,如下图所示:

    {
        text: l('Edit'),
        visible: abp.auth.isGranted('BookStore.Books.Edit'), //CHECK for the PERMISSION
        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"
            )
        )
    );
    

    并用以下内容替换代码块:

    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关键字并重组返回值。最终的BookStoreMenuContributor类应该是下面这样的:

    using System.Threading.Tasks;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Localization;
    using Acme.BookStore.Localization;
    using Acme.BookStore.MultiTenancy;
    using Acme.BookStore.Permissions;
    using Volo.Abp.TenantManagement.Web.Navigation;
    using Volo.Abp.UI.Navigation;
    
    namespace Acme.BookStore.Web.Menus
    {
        public class BookStoreMenuContributor : IMenuContributor
        {
            public async Task ConfigureMenuAsync(MenuConfigurationContext context)
            {
                if (context.Menu.Name == StandardMenus.Main)
                {
                    await ConfigureMainMenuAsync(context);
                }
            }
    
            private async Task ConfigureMainMenuAsync(MenuConfigurationContext context)
            {
                if (!MultiTenancyConsts.IsEnabled)
                {
                    var administration = context.Menu.GetAdministration();
                    administration.TryRemoveMenuItem(TenantManagementMenuNames.GroupName);
                }
    
                var l = context.GetLocalizer<BookStoreResource>();
    
                context.Menu.Items.Insert(0, new ApplicationMenuItem("BookStore.Home", l["Menu: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"
                    ));
                }
            }
        }
    }
    

    {{else if UI == "NG"}}

    Angular Guard配置

    UI的第一步是防止未经授权的用户看到 "书籍 "菜单项并进入书籍管理页面。

    打开/src/app/book/book-routing.module.ts并替换为以下内容:

    import { NgModule } from '@angular/core';
    import { Routes, RouterModule } from '@angular/router';
    import { AuthGuard, PermissionGuard } from '@abp/ng.core';
    import { BookComponent } from './book.component';
    
    const routes: Routes = [
      { path: '', component: BookComponent, canActivate: [AuthGuard, PermissionGuard] },
    ];
    
    @NgModule({
      imports: [RouterModule.forChild(routes)],
      exports: [RouterModule],
    })
    export class BookRoutingModule {}
    
    • @abp/ng.core中导入了AuthGuardPermissionGuard
    • 添加canActivate: [AuthGuard, PermissionGuard]到路由定义中。

    打开/src/app/route.provider.ts,在/books路由中添加requiredPolicy: 'BookStore.Books'/books路由块应该如下:

    {
      path: '/books',
      name: '::Menu:Books',
      parentName: '::Menu:BookStore',
      layout: eLayoutType.application,
      requiredPolicy: 'BookStore.Books',
    }
    

    隐藏新建书籍按钮

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

    image

    打开/src/app/book/book.component.html文件,替换新建按钮的HTML内容,如下所示:

    <!-- Add the abpPermission directive -->
    <button *abpPermission="'BookStore.Books.Create'" id="create" class="btn btn-primary" type="button" (click)="createBook()">
      <i class="fa fa-plus mr-1"></i>
      <span>{%{{{ '::NewBook' | abpLocalization }}}%}</span>
    </button>
    
    • 仅仅添加*abpPermission="'BookStore.Books.Create'",如果当前用户没有权限,就会隐藏这个按钮。

    隐藏编辑和删除操作

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

    image

    如果当前用户没有授予相关权限,我们应该隐藏这个操作。

    打开/src/app/book/book.component.html文件,替换编辑和删除按钮的内容,如下所示:

    <!-- Add the abpPermission directive -->
    <button *abpPermission="'BookStore.Books.Edit'" ngbDropdownItem (click)="editBook(row.id)">
      {%{{{ '::Edit' | abpLocalization }}}%}
    </button>
    
    <!-- Add the abpPermission directive -->
    <button *abpPermission="'BookStore.Books.Delete'" ngbDropdownItem (click)="delete(row.id)">
      {%{{{ '::Delete' | abpLocalization }}}%}
    </button>
    
    • 添加*abpPermission="'BookStore.Books.Edit'",如果当前用户没有编辑权限,会隐藏编辑操作。
    • 添加*abpPermission="'BookStore.Books.Delete'",如果当前用户没有删除权限,会隐藏删除操作。

    {{else if UI == "Blazor"}}

    授权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;
        }
    }
    

    The base AbpCrudPageBase class automatically checks these permissions on the related operations. It also defines the given properties for us if we need to check them manually:

    基类AbpCrudPageBase自动检查这些相关操作的权限。如果我们需要手动检查,它也为我们定义了指定的属性。

    • HasCreatePermission: True, 如果当前用户有权限创建该实体。
    • HasUpdatePermission: True, 如果当前用户有权限编辑/更新该实体。
    • HasDeletePermission: True, 如果当前用户有权限删除该实体。

    Blazor提示:虽然将C#代码添加到@code块中对于简单的代码部分是可以的,但是当代码块变长时,建议使用后台代码的方法来开发一个更可维护的代码库。我们将对作者的部分使用这种方法。

    隐藏新建书籍按钮

    用一个 "if "块包住新建书籍按钮,如下所示:

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

    隐藏编辑/删除动作

    EntityAction component defines Visible attribute (parameter) to conditionally show the action.

    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>
    

    关于权限缓存

    你可以运行并测试这些权限。从管理员角色中删除一个与书有关的权限,可以看到相关的按钮/操作从用户界面中消失。

    ABP框架在客户端缓存当前用户的权限。因此,当你为自己改变权限时,你需要手动刷新页面来使其生效。如果你不刷新并试图使用被禁止的操作,你会从服务端上得到一个HTTP 403(禁止)的响应。

    改变一个角色或用户的权限在服务端会立即生效。所以,这个缓存系统不会造成任何安全问题。

    菜单项

    即使我们已经保护了书籍管理页面的所有层面,它仍然在应用程序的主菜单上可见。如果当前用户没有权限,我们应该隐藏该菜单项。

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

    {{end}}

    下一篇

    见本教程的下一篇

  • 相关阅读:
    hadoop入门学习系列之一hadoop伪分布式模式下安装及运行
    redis主从复制搭建
    Struts2 配置文件result的name属性和type属性
    context:exclude-filter spring事宜【经典-转】
    Incorrect column count: expected 1, actual 2
    SQL must not be null(低级错误)
    Injection of resource dependencies failed解决办法总结
    SpringMVC Controller 介绍【转】
    Json格式化工具 JsonViewer下载
    STS或eclipse安装SVN插件
  • 原文地址:https://www.cnblogs.com/tjubuntu/p/15710466.html
Copyright © 2011-2022 走看看