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