1.简介

ABP vNext框架本身提供了一套权限框架,其功能非常丰富,具体可参考官方文档:https://docs.abp.io/en/abp/latest/Authorization

但是我们使用时会发现,对于正常的单体应用,ABP vNext框架提供的权限系统没有问题, 但是在微服务架构下,这种权限系统并不是非常的友好。

我希望我的权限系统可以满足以下要求:

  1. 每个聚合服务持有独立的权限集合
  2. 每个聚合服务可以独立声明、使用其接口访问所需的权限。
  3. 提供统一接口负责管理、存储所有服务权限并实现对角色的授权。
  4. 每个接口可以灵活组合使用一个或多个权限码。
  5. 权限框架使用尽量简单,减少额外编码量。

在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);
//映射实体类到dto
return 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.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
}
}

添加角色权限关系管理实现类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.Id
select 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>();

完成后运行身份管理服务,可正常运行和访问各接口,则基础服务层修改完成。

3. 公共组件

添加公共类库Demo.Permissions,编辑Demo.Permissions.csproj文件,将 <Project Sdk="Microsoft.NET.Sdk"> 改为:

<Project Sdk="Microsoft.NET.Sdk.Web">

为Demo.Permissions项目添加Nuget引用Volo.Abp.Core和Microsoft.AspNetCore.Http,并应用Demo.Identity.HttpApi.Client项目。

在Demo.Permissions中添加权限关系枚举PermissionRelation如下:

namespace Demo.Permissions;

/// <summary>
/// 权限关系枚举
/// </summary>
public enum PermissionRelation
{
/// <summary>
/// 需要同时满足
/// </summary>
And,
/// <summary>
/// 只需要满足任意一项
/// </summary>
Or, }

在Demo.Permissions中添加CusPermissionAttribute特性,用于标记接口所需要的权限,如下:

namespace Demo.Permissions;

/// <summary>
/// 自定义权限特性
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class CusPermissionAttribute : Attribute
{
/// <summary>
/// 权限编码
/// </summary>
public string[] PermissionCode { get; } /// <summary>
/// 权限之间的关系
/// </summary>
public PermissionRelation Relation { get; } = PermissionRelation.And; /// <summary>
/// 构造函数
/// </summary>
/// <param name="relation">权限关系</param>
/// <param name="permissionCodes">权限编码</param>
public CusPermissionAttribute(PermissionRelation relation,params string[] permissionCodes)
{
Relation = relation;
PermissionCode = permissionCodes;
} /// <summary>
/// 构造函数
/// </summary>
/// <param name="permissionCodes">权限编码</param>
public CusPermissionAttribute(params string[] permissionCodes)
{
PermissionCode = permissionCodes;
}
}

其中一个特性可以声明多个权限码,Relation表示该特性中所有权限码间的关系,如果为And,需要用户具有该特性声明的所有权限码才可通过验证,若为Or,则表示用户只要具有任意一个或多个该特性中声明的权限就可通过验证。一个接口可以声明多个特性,特性与特性之间是And关系。

在Demo.Permissions中添加权限验证中间件CusPermissionMiddleware如下:

using Demo.Identity.Permissions;
using Microsoft.AspNetCore.Http.Features;
using Volo.Abp.Users; namespace Demo.Permissions; /// <summary>
/// 自定义权限中间件
/// </summary>
public class CusPermissionMiddleware
{
private readonly RequestDelegate _next;
private readonly ICurrentUser _currentUser;
private readonly ISysPermissionAppService _service; public CusPermissionMiddleware(RequestDelegate next, ICurrentUser currentUser, ISysPermissionAppService service)
{
_next = next;
_currentUser = currentUser;
_service = service;
} public async Task InvokeAsync(HttpContext context)
{
var attributes =
context.GetEndpoint()?.Metadata.GetOrderedMetadata<CusPermissionAttribute>(); //如果不存在CusPermissionAttribute特性则该接口不需要权限验证,直接跳过
if (attributes==null||attributes.Count==0)
{
await _next(context);
return;
}
//如果需要权限验证则必须是已登录用户,否则返回401
if (_currentUser.Id == null)
{
context.Response.StatusCode = 401;
return;
}
//获取用户权限
var userPermisions = (await _service.GetUserPermissionCode((Guid) _currentUser.Id)).ToHashSet();
//比对权限 如果无权限则返回403
foreach (var cusPermissionAttribute in attributes)
{
var flag = cusPermissionAttribute.Relation == PermissionRelation.And
? cusPermissionAttribute.PermissionCode.All(code => userPermisions.Contains(code))
: cusPermissionAttribute.PermissionCode.Any(code => userPermisions.Contains(code));
if (!flag)
{
context.Response.StatusCode = 403;
return;
}
} await _next(context);
}
}

在接口调用时,该中间件会获取接口所声明的权限特性,并调用身份管理服务接口获取当前用户所持有的权限码,按特性顺序依次验证。

在Demo.Permissions中添加PermissionRegistor类,用于在聚合服务启动时读取代码中声明的所有权限码,并注册到身份管理服务。代码如下:

using System.ComponentModel;
using Demo.Identity.Permissions.Dto; namespace Demo.Permissions; /// <summary>
/// 权限注册
/// </summary>
public static class PermissionRegistor
{ /// <summary>
/// 在指定类型中获取权限集合
/// </summary>
/// <param name="serviceName">服务名称</param>
/// <typeparam name="T">类型</typeparam>
/// <returns></returns>
internal static List<SysPermissionDto> GetPermissions<T>(string serviceName)
{
List<SysPermissionDto> result = new List<SysPermissionDto>();
Type type = typeof(T);
var fields = type.GetFields().Where(x=>x.IsPublic&&x.IsStatic);
foreach (var field in fields)
{
string code = field.GetValue(null).ToString();
string name = "";
object[] objs = field.GetCustomAttributes(typeof(DescriptionAttribute), false); //获取描述属性
if (objs != null && objs.Length > 0)
{
DescriptionAttribute descriptionAttribute = (DescriptionAttribute) objs[0];
name = descriptionAttribute.Description;
} string parentCode = null;
if (code.Contains("."))
{
parentCode = code.Substring(0, code.LastIndexOf('.'));
} result.Add(new SysPermissionDto()
{
Name = name,
Code = code,
ParentCode = parentCode,
ServiceName = serviceName,
});
} return result;
}
}

在Demo.Permissions中添加CusPermissionExtensions类,提供IApplicationBuilder的扩展方法,用于注册中间件和注册权限,代码如下:

using Demo.Identity.Permissions;

namespace Demo.Permissions;

public static class CusPermissionExtensions
{
/// <summary>
/// 注册自定义权限
/// </summary>
public static void UseCusPermissions<T>(this IApplicationBuilder app, string serviceName)
{
app.RegistPermissions<T>(serviceName);
app.UseMiddleware<CusPermissionMiddleware>();
} /// <summary>
/// 注册权限
/// </summary>
/// <param name="app"></param>
/// <param name="serviceName">服务名称</param>
/// <typeparam name="T"></typeparam>
private static async Task RegistPermissions<T>(this IApplicationBuilder app, string serviceName)
{
var service = app.ApplicationServices.GetService<ISysPermissionAppService>();
var permissions = PermissionRegistor.GetPermissions<T>(serviceName);
await service.RegistPermission(serviceName, permissions);
}
}

在Demo.Permissions中添加DemoPermissionsModule类如下:

using Demo.Identity;
using Volo.Abp.Modularity; namespace Demo.Permissions;

[DependsOn(typeof(IdentityHttpApiClientModule))]
public class DemoPermissionsModule:AbpModule
{ }

4. 聚合服务层

在聚合服务层,我们就可以使用刚才创建的Demo.Permissions类库,这里以商城服务为例。

在Demo.Store.Application项目中添加Demo.Permissions的项目引用,并为DemoStoreApplicationModule类添加以下特性:

[DependsOn(typeof(DemoPermissionsModule))]

在Demo.Store.Application项目中添加在PermissionLab类用于声明该服务中用到的所有权限,代码如下

using System.ComponentModel;

namespace Demo.Store.Application;

/// <summary>
/// 权限列表
/// </summary>
public class PermissionLab
{
[Description("订单")]
public const string ORDER = "Order"; [Description("创建订单")]
public const string ORDER_CREATE = $"{ORDER}.Create"; [Description("查询订单")]
public const string ORDER_SELECT = $"{ORDER}.Select"; //添加其他权限 ……
}

这里使用常量定义权限,其中常量的值为权限码,常量名称使用Description特性标记。

在Demo.Store.HttpApi.Host项目配置文件appsettings.json中的RemoteServices中添加身份管理服务地址如下:

"Default": {
"BaseUrl": "http://localhost:5000/"
},

在Demo.Store.HttpApi.Host项目DemoStoreHttpApiHostModule类OnApplicationInitialization方法中找到 app.UseRouting(); ,在其后面添加如下内容:

app.UseCusPermissions<PermissionLab>("Store");

这样我们就可以在聚合服务层ApplicationService的方法上添加CusPermission用于声明接口所需要的权限,例如:

/// <summary>
/// 分页查询订单列表
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
[CusPermission(PermissionLab.ORDER_SELECT)]
public async Task<PagedResultDto<StoreOrderDto>> GetListAsync(PagedAndSortedResultRequestDto input)
{
  var ret = await _orderAppService.GetListAsync(input);
  return new PagedResultDto<StoreOrderDto>
  {
    TotalCount = ret.TotalCount,
    Items = ObjectMapper.Map<IReadOnlyList<OrderDto>, List<StoreOrderDto>>(ret.Items)
  };
}

5.补充说明

完成以上步骤后,我们可以在聚合服务层Admin项目中将身份管理服务中角色权限相关接口封装并暴露给客户端调用,其中注册权限接口仅为聚合服务层注册权限使用,不建议暴露给客户端。

这里我只简单使用了对权限码自身的校验,并未做父子关系的关联校验,在实际项目中,可以依据需要进行修改或扩展。

ABP vNext微服务架构详细教程——分布式权限框架的更多相关文章

  1. [Abp vNext微服务实践] - 添加中文语言

    简介 abp vNext中提供了多语言功能,默认语言是英文,没有提供中文语言包.在业务开发中,定义权限后需要用中文的备注提供角色选择,本篇将介绍如何在abp vNext中加入中文语言. step1:添 ...

  2. [Abp vNext微服务实践] - 文章目录

    简介 ABP vNext是volosoft的新一代框架,ABP(vNext)完全使用.NET CORE和DDD(领域驱动)打造,目前GitHub已有6K+次提交,发布版本超过40次,Nuget包下载量 ...

  3. [Abp vNext微服务实践] - 服务通讯

    简介 服务通讯是微服务架构中必不可少的功能,服务通讯的效率决定了微服务架构的优略.常用的微服务通讯策略有两种,分别是rpc.http,其中rpc以gRpc框架为代表使用者最多.abp vNext微服务 ...

  4. 基于 abp vNext 微服务开发的敏捷应用构建平台 - 项目介绍

    缘起 目前使用ABP框架已经将近3年了,大大小小的项目也陆陆续续做了很多.由于现有信息系统的架构模式是在底层的技术平台上直接构建信息系统并采用技术主导,使用业务无关的编程工具来开发信息系统的缺陷使得系 ...

  5. [Abp vNext微服务实践] - vue-element-admin管理Identity

    一.简介 abp vNext微服务框架中已经提供通用权限和用户角色管理模块,管理UI使用的是MVC模式,不适用于国内主打的MVVM开发模式.在前端框架选型后笔者决定改造abp vNext微服务框架中原 ...

  6. [Abp vNext微服务实践] - 启动流程

    前几篇分别介绍了abp vNext微服务框架和微服务CI/CD环境搭建,本篇开始介绍vNext微服务框架的开发环境搭建. 环境准备 官方介绍的系统架构图如下: 上图中身份服务和网关服务已经集成在系统中 ...

  7. [Abp vNext微服务实践] - 业务开发

    前几篇分别介绍了abp vNext微服务框架.开发环境搭建和vue element admin前端框架接入,在vue element admin中实现用户角色管理基本功能后就可以开始进行业务开发了,本 ...

  8. [Abp vNext微服务实践] - 前后端分类

    一.前景 abp vNext是ABP 开源 Web应用程序框架,是abp的新一代开源web框架.框架完美的集成.net core.identity server4等开源框架,适用于构建web应用程序和 ...

  9. abp vNext微服务框架分析

    本文转载自:https://www.cnblogs.com/william-xu/p/11245738.html abp vNext新框架的热度一直都很高,于是最近上手将vNext的微服务Demo做了 ...

  10. [Abp vNext微服务实践] - 框架分析

    一.简介 abp vNext新框架的热度一直都很高,于是最近上手将vNext的微服务Demo做了一番研究.我的体验是,vNext的微服务架构确实比较成熟,但是十分难以上手,对于没有微服务开发经验的.n ...

随机推荐

  1. 【笔记】DDD实战课-人保架构欧创新

    目录 开篇 学好DDD,你能做什么? 基础 领域驱动设计:微服务设计为什么要选择 DDD? DDD的两层设计 DDD与微服务的关系 领域.子域.核心域.通用域和支撑域:傻傻分不清? 领域和子域 核心域 ...

  2. debug 获取mybatis dao 连接的数据库

    MapperProxy.invoke MapperMethod.execute 查看sqlSession .

  3. 区分CommonJs/ES6 Module/AMD/CMD

    模块加载方式 CommonJs ES6 Module AMD CMD UMD Commonjs和ES6 Module的区别 总结 1.CommonJS CommonJS 是一个项目,其目标是为 Jav ...

  4. 使用CMD创建任意文件

    C:\>fsutil file createnew 用法 : fsutil file createnew <文件名> <长度> 范例:fsutil file create ...

  5. Github快速访问

    Github快速访问 1. 国内访问github慢     github是国外网站,用国内的网络很难访问到,也就无法使用github,作为程序猿的我们,无法使用github可太难受了,那么我们有什么办 ...

  6. MySQL 常用命令(1)------连接、添加用户与授权

    一.连接MySQL 格式: mysql -h主机地址 -u用户名 -p用户密码 1.连接到本机上的MYSQL 进入目录mysql\bin,再键入命令mysql -u root -p,回车后提示你输密码 ...

  7. 为什么JAVA中(byte)128结果为-128;(byte)-129结果为127

    为什么JAVA中(byte)128结果为-128;(byte)-129结果为127 在JAVA中默认的整型为int型,int型占4个字节,为32位.byte占一个字节为8位. JAVA中的二进制都是采 ...

  8. 2022-3-15内部群每日三题-清辉PMP

    1.一家公司被一家大公司收购,这家大公司希望把重点放在其核心产品和服务上.处于规划阶段的所有项目都正在进行修定,以包含新的变更管理程序.一个项目的项目经理需要持续的高级输入,以确保与新的组织结构保持一 ...

  9. CentOS系统 / 目录下每个子目录的作用

    Text. 1./bin 该目录存放root和交互式登录用户使用的二进制可执行文件,如cat,cp,date,rm等. 2./boot 该目录主要存放系统启动所需要的相关文件,如何内核文件vmlinu ...

  10. Elasticsearch Windows版安装配置

    Elasticsearch简介 Elasticsearch是一个开源的搜索文献的引擎,大概含义就是你通过Rest请求告诉它关键字,他给你返回对应的内容,就这么简单. Elasticsearch封装了L ...