ABP vNext微服务架构详细教程——分布式权限框架(上)
1
簡介
ABP vNext框架本身提供了一套權限框架,其功能非常豐富,具體可參考官方文檔:https://docs.abp.io/en/abp/latest/Authorization
但是我們使用時會發現,對于正常的單體應用,ABP vNext框架提供的權限系統沒有問題, 但是在微服務架構下,這種權限系統并不是非常的友好。
我希望我的權限系統可以滿足以下要求:
每個聚合服務持有獨立的權限集合
每個聚合服務可以獨立聲明、使用其接口訪問所需的權限。
提供統一接口負責管理、存儲所有服務權限并實現對角色的授權。
每個接口可以靈活組合使用一個或多個權限碼。
權限框架使用盡量簡單,減少額外編碼量。
在ABP vNext框架基礎上,重新編寫了一套分布式權限框架,大體規則如下:
使用ABP vNext框架中提供的用戶、角色模型不做改變,替代重新定義權限模型,重新定義權限的實體及相關服務接口。
在身份管理服務中,實現權限的統一管理、角色授權和權限認證。
在聚合服務中定義其具有的權限信息、權限關系并通過特性聲明各接口所需要的權限。
在聚合服務啟動時,自動將其權限信息注冊到身份管理服務。
客戶端訪問聚合服務層服務時在聚合服務層中間件中驗證當前用戶是否具有該接口權限,驗證過程需調用身份管理服務對應接口。?
權限系統具體實現見下文。
2
身份認證服務
在之前的文章中我們已經搭建了身份認證服務的基礎框架,這里我們直接在此基礎上新增代碼。
在Demo.Identity.Domain項目中添加Permissions文件夾,并添加Entities子文件夾。在此文件夾下添加實體類SysPermission和RolePermissions如下:
using System; using System.ComponentModel.DataAnnotations; using Volo.Abp.Domain.Entities;namespace Demo.Identity.Permissions.Entities;/// <summary> /// 權限實體類 /// </summary> public class SysPermission : Entity<Guid> {/// <summary>/// 服務名稱/// </summary>[MaxLength(64)]public string ServiceName { get; set; }/// <summary>/// 權限編碼/// </summary>[MaxLength(128)]public string Code { get; set; }/// <summary>/// 權限名稱/// </summary>[MaxLength(64)]public string Name { get; set; }/// <summary>/// 上級權限ID/// </summary>[MaxLength(128)]public string ParentCode { get; set; }/// <summary>/// 判斷兩個權限是否相同/// </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>/// 設置ID的值/// </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> /// 角色權限對應關系 /// </summary> public class RolePermissions : Entity<Guid> {/// <summary>/// 角色編號/// </summary>public Guid RoleId { get; set; }/// <summary>/// 權限編號/// </summary>public Guid PermissionId { get; set; } }將Demo.Identity.Application.Contracts項目中原有Permissions文件夾中所有類刪除,并添加子文件夾Dto。在此文件夾下添加SysPermissionDto、PermissionTreeDto、SetRolePermissionsDto
類如下:
using System; using System.ComponentModel.DataAnnotations; using Volo.Abp.Application.Dtos;namespace Demo.Identity.Permissions.Dto;/// <summary> /// 權限DTO /// </summary> public class SysPermissionDto:EntityDto<Guid> {/// <summary>/// 服務名稱/// </summary>[MaxLength(64)]public string ServiceName { get; set; }/// <summary>/// 權限編碼/// </summary>[MaxLength(128)]public string Code { get; set; }/// <summary>/// 權限名稱/// </summary>[MaxLength(64)]public string Name { get; set; }/// <summary>/// 上級權限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> /// 權限樹DTO /// </summary> public class PermissionTreeDto : EntityDto<Guid> {/// <summary>/// 服務名稱/// </summary>public string ServiceName { get; set; }/// <summary>/// 權限編碼/// </summary>public string Code { get; set; }/// <summary>/// 權限名稱/// </summary>public string Name { get; set; }/// <summary>/// 上級權限ID/// </summary>public string ParentCode { get; set; }/// <summary>/// 子權限/// </summary>public List<PermissionTreeDto> Children { get; set; }}using System; using System.Collections.Generic;namespace Demo.Identity.Permissions.Dto;/// <summary> /// 設置角色權限DTO /// </summary> public class SetRolePermissionsDto {/// <summary>/// 角色編號/// </summary>public Guid RoleId { get; set; }/// <summary>/// 權限ID列表/// </summary>public List<Guid> Permissions { get; set; } }將Demo.Identity.Application.Contracts項目中Permissions文件夾下添加接口IRolePermissionsAppService如下:
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> /// 角色管理應用服務接口 /// </summary> public interface IRolePermissionsAppService: IApplicationService {/// <summary>/// 獲取角色所有權限/// </summary>/// <param name="roleId">角色ID</param>/// <returns></returns>Task<List<PermissionTreeDto>> GetPermission(Guid roleId);/// <summary>/// 設置角色權限/// </summary>/// <param name="dto">角色權限信息</param>/// <returns></returns>Task SetPermission(SetRolePermissionsDto dto); }將Demo.Identity.Application.Contracts項目中Permissions文件夾下添加接口ISysPermissionAppService如下:
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> /// 權限管理應用服務接口 /// </summary> public interface ISysPermissionAppService:IApplicationService {/// <summary>/// 按服務注冊權限/// </summary>/// <param name="serviceName">服務名稱</param>/// <param name="permissions">權限列表</param>/// <returns></returns>Task<bool> RegistPermission(string serviceName, List<SysPermissionDto> permissions);/// <summary>/// 按服務獲取權限/// </summary>/// <param name="serviceName">服務名稱</param>/// <returns>查詢結果</returns>Task<List<SysPermissionDto>> GetPermissions(string serviceName);/// <summary>/// 獲取完整權限樹/// </summary>/// <param name="Permission"></param>/// <returns>查詢結果</returns>Task<List<PermissionTreeDto>> GetPermissionTree();/// <summary>/// 獲取用戶權限碼/// </summary>/// <param name="userId">用戶編號</param>/// <returns>查詢結果</returns>Task<List<string>> GetUserPermissionCode(Guid userId); }在公共類庫文件夾common中創建.Net6類庫項目項目Demo.Core,用于存放通用類。
這里我們在Demo.Core中添加文件夾CommonExtension用于存放通用擴展,添加EnumExtensions和ListExtensions類如下:
namespace Demo.Core.CommonExtension;/// <summary> /// 枚舉擴展類 /// </summary> public static class EnumExtensions {/// <summary>/// 獲取描述特性/// </summary>/// <param name="enumValue">枚舉值</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); //獲取描述屬性if (objs == null || objs.Length == 0) //當描述屬性沒有時,直接返回名稱return value;DescriptionAttribute descriptionAttribute = (DescriptionAttribute)objs[0];return descriptionAttribute.Description;} }namespace Demo.Core.CommonExtension;public static class ListExtensions {/// <summary>/// 集合去重/// </summary>/// <param name="lst">目標集合</param>/// <param name="keySelector">去重關鍵字</param>/// <typeparam name="T">集合元素類型</typeparam>/// <typeparam name="TKey">去重關鍵字數據類型</typeparam>/// <returns>去重結果</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;} }在Demo.Core項目中添加文件夾CommonFunction用于存放通用方法,這里我們添加用于集合比對的ListCompare類如下:
using VI.Core.CommonExtension;namespace VI.Core.CommonFunction;/// <summary> /// 集合比對 /// </summary> public class ListCompare {/** 調用實例:* MutiCompare<Permission, string>(lst1, lst2, x => x.Code, (obj, isnew) =>* {* if (isnew)* {* Console.WriteLine($"新增項{obj.Id}");* }* else* {* Console.WriteLine($"已存在{obj.Id}");* }* }, out var lstNeedRemove);*//// <summary>/// 對比源集合和目標集合,處理已有項和新增項,并找出需要刪除的項/// </summary>/// <param name="lstSource">源集合</param>/// <param name="lstDestination">目標集合</param>/// <param name="keySelector">集合比對關鍵字</param>/// <param name="action">新增或已有項處理方法,參數:(數據項, 是否是新增)</param>/// <param name="needRemove">需要刪除的數據集</param>/// <typeparam name="TObject">集合對象數據類型</typeparam>/// <typeparam name="TKey">對比關鍵字數據類型</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){//目標集合去重lstDestination.Distinct(keySelector);//將源集合存入字典,提高查詢效率needRemove = new Dictionary<TKey, TObject>();foreach (var item in lstSource){needRemove.Add(keySelector(item),item);}//遍歷目標集合,區分新增項及已有項//在字典中排除目標集合中的項,剩余的即為源集合中需刪除的項foreach (var item in lstDestination){if (needRemove.ContainsKey(keySelector(item))){action(item, false);needRemove.Remove(keySelector(item));}else{action(item, true);}}} }在Demo.Identity.Application項目中添加Permissions文件夾。
在Demo.Identity.Application項目Permissions文件夾中添加PermissionProfileExtensions類用于定義對象映射關系如下:
using Demo.Identity.Permissions.Dto; using Demo.Identity.Permissions.Entities;namespace Demo.Identity.Permissions;public static class PermissionProfileExtensions {/// <summary>/// 創建權限領域相關實體映射關系/// </summary>/// <param name="profile"></param>public static void CreatePermissionsMap(this IdentityApplicationAutoMapperProfile profile){profile.CreateMap<SysPermission, PermissionTreeDto>();profile.CreateMap<SysPermission,SysPermissionDto>();profile.CreateMap<SysPermissionDto,SysPermission>();} }在Demo.Identity.Application項目IdentityApplicationAutoMapperProfile類的IdentityApplicationAutoMapperProfile方法中添加如下代碼:
this.CreatePermissionsMap();在Demo.Identity.Application項目Permissions文件夾中添加PermissionTreeBuilder類,定義構造權限樹形結構的通用方法如下:
using System.Collections.Generic; using System.Linq; using Demo.Identity.Permissions.Dto;namespace Demo.Identity.Permissions;/// <summary> /// 權限建樹幫助類 /// </summary> public static class PermissionTreeBuilder {/// <summary>/// 建立樹形結構/// </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;} }之后我們在Demo.Identity.Application項目Permissions文件夾中添加權限管理實現類SysPermissionAppService如下:
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>/// 權限管理應用服務/// </summary>public class SysPermissionAppService : IdentityAppService, ISysPermissionAppService{#region 初始化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 按服務注冊權限/// <summary>/// 按服務注冊權限/// </summary>/// <param name="serviceName">服務名稱</param>/// <param name="permissions">權限列表</param>/// <returns></returns>public async Task<bool> RegistPermission(string serviceName, List<SysPermissionDto> permissions){//根據服務名稱查詢現有權限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){//新增await _sysPermissionsRepository.InsertAsync(entity);}else{//修改var tmp = lst.FirstOrDefault(x => x.Code == entity.Code);//調用權限判斷方法,如果code和name相同就不進行添加if (!entity.Equals(tmp)&&tmp!=null){entity.SetId(tmp.Id);await _sysPermissionsRepository.UpdateAsync(entity);}}}, out var needRemove);foreach (var item in needRemove){//刪除多余項await _sysPermissionsRepository.DeleteAsync(item.Value);}return true;}#endregion#region 按服務獲取權限/// <summary>/// 按服務獲取權限/// </summary>/// <param name="serviceName">服務名稱</param>/// <returns>查詢結果</returns>public async Task<List<SysPermissionDto>> GetPermissions(string serviceName){var query = (await _sysPermissionsRepository.GetQueryableAsync()).Where(x => x.ServiceName == serviceName);//使用AsyncExecuter進行異步查詢var lst = await AsyncExecuter.ToListAsync(query);//映射實體類到dtoreturn ObjectMapper.Map<List<SysPermission>, List<SysPermissionDto>>(lst);}#endregion#region 獲取完整權限樹/// <summary>/// 獲取完整權限樹/// </summary>/// <returns>查詢結果</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 獲取用戶權限碼/// <summary>/// 獲取用戶權限碼/// </summary>/// <param name="userId">用戶編號</param>/// <returns>查詢結果</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.RoleIdjoin pe in (await _sysPermissionsRepository.GetQueryableAsync()) on rp.PermissionId equals pe.Idselect pe.Code;var permission = await AsyncExecuter.ToListAsync(query);return permission.Distinct(x=>x);}#endregion} }添加角色權限關系管理實現類RolePermissionsAppService如下:
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>/// 角色管理應用服務/// </summary>public class RolePermissionsAppService : IdentityAppService, IRolePermissionsAppService{#region 初始化private readonly IRepository<RolePermissions> _rolePermissionsRepository;private readonly IRepository<SysPermission> _sysPermissionsRepository;public RolePermissionsAppService(IRepository<RolePermissions> rolePermissionsRepository,IRepository<SysPermission> sysPermissionsRepository ){_rolePermissionsRepository = rolePermissionsRepository;_sysPermissionsRepository = sysPermissionsRepository;}#endregion#region 獲取角色所有權限/// <summary>/// 獲取角色所有權限/// </summary>/// <param name="roleId">角色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.Idselect permission;var permissions = await AsyncExecuter.ToListAsync(query);var lst = ObjectMapper.Map<List<SysPermission>, List<PermissionTreeDto>>(permissions);return PermissionTreeBuilder.Build(lst);}#endregion#region 設置角色權限/// <summary>/// 設置角色權限/// </summary>/// <param name="roleId">橘色編號</param>/// <param name="permissions">權限編號</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} }?在Demo.Identity.EntityFrameworkCore項目IdentityDbContext類中加入以下屬性:
public DbSet<SysPermission> SysPermissions { get; set; } public DbSet<RolePermissions> RolePermissions { get; set; }在Demo.Identity.EntityFrameworkCore項目目錄下啟動命令提示符,執行以下命令分別創建和執行數據遷移:
dotnet-ef migrations add AddPermissions dotnet-ef database update在Demo.Identity.EntityFrameworkCore項目IdentityEntityFrameworkCoreModule類ConfigureServices方法中找到?options.AddDefaultRepositories(includeAllEntities:?true);?,在其后面加入以下代碼:
options.AddDefaultRepository<IdentityUserRole>();完成后運行身份管理服務,可正常運行和訪問各接口,則基礎服務層修改完成。后續操作請看下一篇
end
更多精彩
關注我獲得
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的ABP vNext微服务架构详细教程——分布式权限框架(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于C#的计时管理器
- 下一篇: 我做了一个 Istio Workshop