当前位置:网站首页>ABP vNext microservice architecture detailed tutorial - distributed permission framework (Part 1)

ABP vNext microservice architecture detailed tutorial - distributed permission framework (Part 1)

2022-07-05 03:39:00 Dotnet cross platform

1

brief introduction

3e5764ada323a9ea07d18cdf184d98f9.png

ABP vNext The framework itself provides a set of permission framework , Its functions are very rich , For details, please refer to the official documents :https://docs.abp.io/en/abp/latest/Authorization

0768681cd19340aa43ba9743ea22e945.gif

But when we use it, we will find , For normal monomer application ,ABP vNext There is no problem with the permission system provided by the framework , But under the microservice Architecture , This permission system is not very friendly .

a58eca97ed79796317a83504dc349bee.gif

I hope my permission system can meet the following requirements :

Each aggregation service holds an independent set of permissions

Each aggregation service can be declared independently 、 Use its interface to access the required permissions .

Provide a unified interface to manage 、 Store all service permissions and authorize roles .

Each interface can flexibly combine and use one or more permission codes .

Use the permission framework as simple as possible , Reduce the amount of extra coding .

8934846192858daf5531d12bde980b57.gif

stay ABP vNext Based on the framework , Rewrite a set of distributed permission framework , The general rules are as follows :

Use ABP vNext Users provided in the framework 、 The role model does not change , Instead, redefine the permission model , Redefine the entities of permissions and related service interfaces .

In identity management services , Realize unified management of permissions 、 Role authorization and authority authentication .

Define the permission information it has in the aggregation service 、 Permission relationship and declare the permissions required by each interface through characteristics .

When the aggregation service starts , Automatically register its permission information to the identity management service .

When the client accesses the aggregated service layer service, verify whether the current user has the interface permission in the aggregated service layer middleware , The authentication process needs to call the corresponding interface of the identity management service . 

796fc6bddb913ceedc406e955c9db6a3.gif

The specific implementation of permission system is shown below .

2

Identity authentication service

2be48d4482e970fa122386e05f90f3fa.gif

In the previous article, we have built the basic framework of identity authentication service , Here we add code directly on this basis .

0063425d49ab05abe57474c708b9a62d.gif

stay Demo.Identity.Domain Add to the project Permissions Folder , And add Entities A folder . Add entity classes under this folder SysPermission and RolePermissions as follows :

using System;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Domain.Entities;


namespace Demo.Identity.Permissions.Entities;


/// <summary>
///  Permission entity class 
/// </summary>
public class SysPermission : Entity<Guid>
{
    /// <summary>
    ///  The service name 
    /// </summary>
    [MaxLength(64)]
    public string ServiceName { get; set; }


    /// <summary>
    ///  Authority code 
    /// </summary>
    [MaxLength(128)]
    public string Code { get; set; }


    /// <summary>
    ///  Permission to name 
    /// </summary>
    [MaxLength(64)]
    public string Name { get; set; }


    /// <summary>
    ///  Superior authority ID
    /// </summary>
    [MaxLength(128)]
    public string ParentCode { get; set; }




    /// <summary>
    ///  Judge whether the two permissions are the same 
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    public override bool Equals(object? obj)
{
        return obj is SysPermission permission
               && permission.ServiceName == ServiceName
               && permission.Name == Name
               && permission.Code == Code
               && permission.ParentCode == ParentCode;
    }


    /// <summary>
    ///  Set up ID Value 
    /// </summary>
    /// <param name="id"></param>
    public void SetId(Guid id)
{
        Id = id;
    }
}
using System;
using Volo.Abp.Domain.Entities;


namespace Demo.Identity.Permissions.Entities;


/// <summary>
///  Role permission correspondence 
/// </summary>
public class RolePermissions : Entity<Guid>
{
    /// <summary>
    ///  Role number 
    /// </summary>
    public Guid RoleId { get; set; }


    /// <summary>
    ///  Authority number 
    /// </summary>
    public Guid PermissionId { get; set; }
}

02f6bdec93f849b88bd8485abbdf1cd9.gif

take Demo.Identity.Application.Contracts The project has Permissions Delete all classes in the folder , And add subfolders Dto. Add... Under this folder SysPermissionDto、PermissionTreeDto、SetRolePermissionsDto

The categories are as follows :

using System;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Application.Dtos;


namespace Demo.Identity.Permissions.Dto;


/// <summary>
///  jurisdiction DTO
/// </summary>
public class SysPermissionDto:EntityDto<Guid>
{
    /// <summary>
    ///  The service name 
    /// </summary>
    [MaxLength(64)]
    public string ServiceName { get; set; }
        
    /// <summary>
    ///  Authority code 
    /// </summary>
    [MaxLength(128)]
    public string Code { get; set; }


    /// <summary>
    ///  Permission to name 
    /// </summary>
    [MaxLength(64)]
    public string Name { get; set; }


    /// <summary>
    ///      Superior authority ID
    /// </summary>
    [MaxLength(128)]
    public string ParentCode { get; set; }
}
using System;
using System.Collections.Generic;
using Volo.Abp.Application.Dtos;


namespace Demo.Identity.Permissions.Dto;


/// <summary>
///  Permission tree DTO
/// </summary>
public class PermissionTreeDto : EntityDto<Guid>
{
    /// <summary>
    ///  The service name 
    /// </summary>
    public string ServiceName { get; set; }


    /// <summary>
    ///  Authority code 
    /// </summary>
    public string Code { get; set; }


    /// <summary>
    ///  Permission to name 
    /// </summary>
    public string Name { get; set; }


    /// <summary>
    ///  Superior authority ID
    /// </summary>
    public string ParentCode { get; set; }


    /// <summary>
    ///  Sub permission 
    /// </summary>
    public List<PermissionTreeDto> Children { get; set; }
        
}
using System;
using System.Collections.Generic;


namespace Demo.Identity.Permissions.Dto;


/// <summary>
///  Set role permissions DTO
/// </summary>
public class SetRolePermissionsDto
{
    /// <summary>
    ///  Role number 
    /// </summary>
    public Guid RoleId { get; set; }


    /// <summary>
    ///  jurisdiction ID list 
    /// </summary>
    public List<Guid> Permissions { get; set; }
}

282a8c294ce4f62ab02dd1a7d3dc66ba.gif

take Demo.Identity.Application.Contracts In the project Permissions Add an interface under the folder IRolePermissionsAppService as follows :

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Demo.Identity.Permissions.Dto;
using Volo.Abp.Application.Services;


namespace Demo.Identity.Permissions;


/// <summary>
///      Role management application service interface 
/// </summary>
public interface IRolePermissionsAppService
    : IApplicationService
{


    /// <summary>
    ///  Get all permissions of the role 
    /// </summary>
    /// <param name="roleId"> role ID</param>
    /// <returns></returns>
    Task<List<PermissionTreeDto>> GetPermission(Guid roleId);




    /// <summary>
    ///  Set role permissions 
    /// </summary>
    /// <param name="dto"> Role permission information </param>
    /// <returns></returns>
    Task SetPermission(SetRolePermissionsDto dto);
}

33cb301ffb60cce4ba8bad696d3bd99c.gif

take Demo.Identity.Application.Contracts In the project Permissions Add an interface under the folder ISysPermissionAppService as follows :

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Demo.Identity.Permissions.Dto;
using Volo.Abp.Application.Services;


namespace Demo.Identity.Permissions;


/// <summary>
///  Permission management application service interface  
/// </summary>
public interface ISysPermissionAppService:IApplicationService
{
    /// <summary>
    ///  Register permissions by service 
    /// </summary>
    /// <param name="serviceName"> The service name </param>
    /// <param name="permissions"> Permission list </param>
    /// <returns></returns>
    Task<bool> RegistPermission(string serviceName, List<SysPermissionDto> permissions);


    /// <summary>
    ///  Get permissions by service 
    /// </summary>
    /// <param name="serviceName"> The service name </param>
    /// <returns> Query results </returns>
    Task<List<SysPermissionDto>> GetPermissions(string serviceName);


    /// <summary>
    ///  Get the full permission tree 
    /// </summary>
    /// <param name="Permission"></param>
    /// <returns> Query results </returns>
    Task<List<PermissionTreeDto>> GetPermissionTree();


    /// <summary>
    ///  Get user permission code 
    /// </summary>
    /// <param name="userId"> The user id </param>
    /// <returns> Query results </returns>
    Task<List<string>> GetUserPermissionCode(Guid userId);
}

a842c0641ab27358777cf7627f48ef31.gif

In the public class library folder common Created in .Net6 Class library project Demo.Core, Used to store general classes .

3278852ff639c4fe7f82826c9a3e471c.gif

Here we are Demo.Core Add folder in CommonExtension Used to store general extensions , add to EnumExtensions and ListExtensions The categories are as follows :

namespace Demo.Core.CommonExtension;


/// <summary>
///  Enumerating extension classes 
/// </summary>
public static class EnumExtensions
{
    /// <summary>
    ///  Get description properties 
    /// </summary>
    /// <param name="enumValue"> Enumerated values </param>
    /// <returns></returns>
    public static string GetDescription(this Enum enumValue)
    {
        string value = enumValue.ToString();
        FieldInfo field = enumValue.GetType().GetField(value);
        object[] objs = field.GetCustomAttributes(typeof(DescriptionAttribute), false);  // Get description properties 
        if (objs == null || objs.Length == 0)  // When there is no description attribute , Directly return the name 
            return value;
        DescriptionAttribute descriptionAttribute = (DescriptionAttribute)objs[0];
        return descriptionAttribute.Description;
    }
}
namespace Demo.Core.CommonExtension;


public static class ListExtensions
{
    /// <summary>
    ///  Gather to get rid of the heavy 
    /// </summary>
    /// <param name="lst"> Target set </param>
    /// <param name="keySelector"> Remove keywords </param>
    /// <typeparam name="T"> Collection element type </typeparam>
    /// <typeparam name="TKey"> De duplication keyword data type </typeparam>
    /// <returns> De duplication results </returns>
    public static List<T> Distinct<T,TKey>(this List<T> lst,Func<T, TKey> keySelector)
    {
        List<T> result = new List<T>();
        HashSet<TKey> set = new HashSet<TKey>();
        foreach (var item in lst)
        {
            var key = keySelector(item);
            if (!set.Contains(key))
            {
                set.Add(key);
                result.Add(item);
            }
        }
        return result;
    }
}

84a26677b92c9a80074d86c7de486d64.gif

stay Demo.Core Add a folder to the project CommonFunction For storing general methods , Here we add ListCompare The categories are as follows :

using VI.Core.CommonExtension;


namespace VI.Core.CommonFunction;


/// <summary>
///  Set comparison 
/// </summary>
public class ListCompare
{
    /*
     *  Call the instance :
     *  MutiCompare<Permission, string>(lst1, lst2, x => x.Code, (obj, isnew) =>
     *  {
     *      if (isnew)
     *      {
     *          Console.WriteLine($" What's new {obj.Id}");
     *      }
     *      else
     *      {
     *          Console.WriteLine($" Already exists {obj.Id}");
     *      }
     *  }, out var lstNeedRemove);
     */
    /// <summary>
    ///  Compare the source set with the target set , Deal with existing items and new items , And find the items that need to be deleted 
    /// </summary>
    /// <param name="lstSource"> Source set </param>
    /// <param name="lstDestination"> Target set </param>
    /// <param name="keySelector"> Set comparison keywords </param>
    /// <param name="action"> New or existing item processing methods , Parameters :( Data item ,  Whether to add )</param>
    /// <param name="needRemove"> Data set to be deleted </param>
    /// <typeparam name="TObject"> Collection object data type </typeparam>
    /// <typeparam name="TKey"> Compare keyword data types </typeparam>
    public static void MutiCompare<TObject,TKey>(List<TObject> lstDestination,List<TObject> lstSource,
        Func<TObject, TKey> keySelector,
        Action<TObject, bool> action, 
        out Dictionary<TKey, TObject> needRemove)
    {
        // Target set de duplication 
        lstDestination.Distinct(keySelector);
        // Store the source set in the dictionary , Improve query efficiency 
        needRemove = new Dictionary<TKey, TObject>();
        foreach (var item in lstSource)
        {
            needRemove.Add(keySelector(item),item);
        }
        // Traverse the target set , Distinguish between new items and existing items 
        // Exclude items in the target set from the dictionary , The remaining items are the items to be deleted in the source collection 
        foreach (var item in lstDestination)
        {
            if (needRemove.ContainsKey(keySelector(item)))
            {
                action(item, false);
                needRemove.Remove(keySelector(item));
            }
            else
            {
                action(item, true);
            }
        }
    }
}

774eda5f91fa5f9f23e62b8ce752335d.gif

stay Demo.Identity.Application Add to the project Permissions Folder .

316da11e90fa1461b08d804effc01b68.gif

stay Demo.Identity.Application project Permissions Add in folder PermissionProfileExtensions Class is used to define object mapping relationships as follows :

using Demo.Identity.Permissions.Dto;
using Demo.Identity.Permissions.Entities;


namespace Demo.Identity.Permissions;


public static class PermissionProfileExtensions
{
    /// <summary>
    ///  Create entity mapping relationships related to permission fields 
    /// </summary>
    /// <param name="profile"></param>
    public static void CreatePermissionsMap(this IdentityApplicationAutoMapperProfile profile)
    {
        profile.CreateMap<SysPermission, PermissionTreeDto>();
        profile.CreateMap<SysPermission,SysPermissionDto>();
        profile.CreateMap<SysPermissionDto,SysPermission>();
    }
}

8bc6c8668e75a02b4f5c412b9542eba5.gif

stay Demo.Identity.Application project IdentityApplicationAutoMapperProfile Class IdentityApplicationAutoMapperProfile Add the following code to the method :

this.CreatePermissionsMap();

75171c85c6a87c6f43a105900b24fdff.gif

stay Demo.Identity.Application project Permissions Add in folder PermissionTreeBuilder class , The general method of defining and constructing permission tree structure is as follows :

using System.Collections.Generic;
using System.Linq;
using Demo.Identity.Permissions.Dto;


namespace Demo.Identity.Permissions;


/// <summary>
///  Permission tree help class 
/// </summary>
public static class PermissionTreeBuilder
{
    /// <summary>
    ///  Build a tree structure 
    /// </summary>
    /// <param name="lst"></param>
    /// <returns></returns>
    public static List<PermissionTreeDto> Build(List<PermissionTreeDto> lst)
    {
        var result = lst.ToList();


        for (var i = 0; i < result.Count; i++)
        {
            if (result[i].ParentCode == null)
            {
                continue;
            }
            foreach (var item in lst)
            {
                item.Children ??= new List<PermissionTreeDto>();
                if (item.Code != result[i].ParentCode)
                {
                    continue;
                }


                item.Children.Add(result[i]);
                result.RemoveAt(i);
                i--;
                break;
            }
        }
        return result;
    }
}

030c797a2c1931482430a4de28c4d644.gif

Then we were in Demo.Identity.Application project Permissions Add the permission management implementation class in the folder SysPermissionAppService as follows :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Demo.Core.CommonFunction;
using Demo.Identity.Permissions.Dto;
using Demo.Identity.Permissions.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Identity;
using Demo.Core.CommonExtension;


namespace Demo.Identity.Permissions
{
    /// <summary>
    ///  Rights management application service 
    /// </summary>
    public class SysPermissionAppService : IdentityAppService, ISysPermissionAppService
    {
        #region  initialization 


        private readonly IRepository<RolePermissions> _rolePermissionsRepository;
        private readonly IRepository<SysPermission> _sysPermissionsRepository;
        private readonly IRepository<IdentityUserRole> _userRolesRepository;


        public SysPermissionAppService(
            IRepository<RolePermissions> rolePermissionsRepository,
            IRepository<SysPermission> sysPermissionsRepository,
            IRepository<IdentityUserRole> userRolesRepository
)
        {
            _rolePermissionsRepository = rolePermissionsRepository;
            _sysPermissionsRepository = sysPermissionsRepository;
            _userRolesRepository = userRolesRepository;
        }


        #endregion
        
        #region  Register permissions by service 


        /// <summary>
        ///  Register permissions by service 
        /// </summary>
        /// <param name="serviceName"> The service name </param>
        /// <param name="permissions"> Permission list </param>
        /// <returns></returns>
        public async Task<bool> RegistPermission(string serviceName, List<SysPermissionDto> permissions)
        {
            // Query existing permissions by service name 
            var entities = await AsyncExecuter.ToListAsync( 
                (await _sysPermissionsRepository.GetQueryableAsync()).Where(c => c.ServiceName == serviceName)
            );
            var lst = ObjectMapper.Map<List<SysPermissionDto>, List<SysPermission>>(permissions);
            ListCompare.MutiCompare(lst, entities, x => x.Code, async (entity, isNew) =>
            {
                if (isNew)
                {
                    // newly added 
                    await _sysPermissionsRepository.InsertAsync(entity);
                }
                else
                {
                    // modify 
                    var tmp = lst.FirstOrDefault(x => x.Code == entity.Code);
                    // Call the permission judgment method , If code and name If it is the same, it will not be added 
                    if (!entity.Equals(tmp)&&tmp!=null)
                    {
                        entity.SetId(tmp.Id);
                        await _sysPermissionsRepository.UpdateAsync(entity);
                    }
                }
            }, out var needRemove);
            foreach (var item in needRemove)
            {
                // Delete extra items 
                await _sysPermissionsRepository.DeleteAsync(item.Value);
            }
            return true;
        }


        #endregion


        #region  Get permissions by service 


        /// <summary>
        ///      Get permissions by service 
        /// </summary>
        /// <param name="serviceName"> The service name </param>
        /// <returns> Query results </returns>
        public async Task<List<SysPermissionDto>> GetPermissions(string serviceName)
        {
            var query = (await _sysPermissionsRepository.GetQueryableAsync()).Where(x => x.ServiceName == serviceName);
            // Use AsyncExecuter Do asynchronous queries 
            var lst = await AsyncExecuter.ToListAsync(query);
            // Map entity classes to dto
            return ObjectMapper.Map<List<SysPermission>, List<SysPermissionDto>>(lst);
        }


        #endregion


        #region  Get the full permission tree 


        /// <summary>
        ///  Get the full permission tree 
        /// </summary>
        /// <returns> Query results </returns>
        public async Task<List<PermissionTreeDto>> GetPermissionTree()
        {
            var per = await _sysPermissionsRepository.ToListAsync();
            var lst = ObjectMapper.Map<List<SysPermission>, List<PermissionTreeDto>>(per);
            return PermissionTreeBuilder.Build(lst);
        }


        #endregion


        #region  Get user permission code 


        /// <summary>
        ///  Get user permission code 
        /// </summary>
        /// <param name="userId"> The user id </param>
        /// <returns> Query results </returns>
        public async Task<List<string>> GetUserPermissionCode(Guid userId)
        {
            var query = from user in (await _userRolesRepository.GetQueryableAsync()).Where(c => c.UserId == userId)
                join rp in (await _rolePermissionsRepository.GetQueryableAsync()) on user.RoleId equals rp.RoleId
                join pe in (await _sysPermissionsRepository.GetQueryableAsync()) on rp.PermissionId equals pe.Id
                select pe.Code;
            var permission = await AsyncExecuter.ToListAsync(query);
            return permission.Distinct(x=>x);
        }


        #endregion
    }
}

2e9636e35cd687e69a7bda0f414e2491.gif

Add the role permission relationship management implementation class RolePermissionsAppService as follows :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Demo.Identity.Permissions.Dto;
using Demo.Identity.Permissions.Entities;
using Volo.Abp.Domain.Repositories;


namespace Demo.Identity.Permissions
{
    /// <summary>
    ///  Role management application service 
    /// </summary>
    public class RolePermissionsAppService : IdentityAppService, IRolePermissionsAppService
    {
        #region  initialization 
        private readonly IRepository<RolePermissions> _rolePermissionsRepository;
        private readonly IRepository<SysPermission> _sysPermissionsRepository;
        
        public RolePermissionsAppService(
            IRepository<RolePermissions> rolePermissionsRepository,
            IRepository<SysPermission> sysPermissionsRepository
)
        {
            _rolePermissionsRepository = rolePermissionsRepository;
            _sysPermissionsRepository = sysPermissionsRepository;
        }
        #endregion




        #region  Get all permissions of the role 
        /// <summary>
        ///  Get all permissions of the role 
        /// </summary>
        /// <param name="roleId"> role ID</param>
        /// <returns></returns>
        public async Task<List<PermissionTreeDto>> GetPermission(Guid roleId)
        {
            var query = from rp in (await _rolePermissionsRepository.GetQueryableAsync())
                    .Where(x => x.RoleId == roleId)
                join permission in (await _sysPermissionsRepository.GetQueryableAsync())
                    on rp.PermissionId equals permission.Id
                select permission;
            var permissions = await AsyncExecuter.ToListAsync(query);
            var lst = ObjectMapper.Map<List<SysPermission>, List<PermissionTreeDto>>(permissions);
            return PermissionTreeBuilder.Build(lst);
        }
        #endregion


        #region  Set role permissions 
        /// <summary>
        ///  Set role permissions 
        /// </summary>
        /// <param name="roleId"> Orange number </param>
        /// <param name="permissions"> Authority number </param>
        /// <returns></returns>
        public async Task SetPermission(SetRolePermissionsDto dto)
        {
            await _rolePermissionsRepository.DeleteAsync(x => x.RoleId == dto.RoleId);
            foreach (var permissionId in dto.Permissions)
            {
                RolePermissions entity = new RolePermissions()
                {
                    PermissionId = permissionId,
                    RoleId = dto.RoleId,
                };
                await _rolePermissionsRepository.InsertAsync(entity);
            }
        }
        #endregion


    }
}

533db24a7e27c1df73b6eb3a7547613b.gif

  stay Demo.Identity.EntityFrameworkCore project IdentityDbContext Add the following attributes to the class :

public DbSet<SysPermission> SysPermissions { get; set; }
public DbSet<RolePermissions> RolePermissions { get; set; }

541fee81fe0e6e01440cada0ca593859.gif

stay Demo.Identity.EntityFrameworkCore Start the command prompt under the project directory , Execute the following commands to create and execute data migration :

dotnet-ef migrations add AddPermissions
dotnet-ef database update

7b7b9ffcbbf1363fbedb091b3b45329d.gif

stay Demo.Identity.EntityFrameworkCore project IdentityEntityFrameworkCoreModule class ConfigureServices Method  options.AddDefaultRepositories(includeAllEntities: true); , Add the following code after it :

options.AddDefaultRepository<IdentityUserRole>();

51cc46674ce16b0cac6aa979d588dcf6.png

Run the identity management service after completion , It can operate normally and access all interfaces , Then the basic service layer is modified . See the next article for follow-up

f8e71b4940c5412ab101fd7014ee2fb3.png

end

781c6fa3e12d116dbb90390fd14e7a12.png

cc34818bd82fcfd1bb2808e7b6dbe677.png

5b994a5a44dd2ca3f1d7d13201560b2a.png

More exciting

Follow me to get

4d2710f8e7134bc22784242e2f96ca64.png

原网站

版权声明
本文为[Dotnet cross platform]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/02/202202140728263803.html