3 公共组件
添加公共类库Demo.Permissions,编辑Demo.Permissions.csproj文件,将 <Project Sdk="Microsoft.NET.Sdk"> 改为:
<Project Sdk="Microsoft.NET.Sdk.Web">在Demo.Permissions中添加权限关系枚举PermissionRelation如下:
namespace Demo.Permissions;/// <summary>/// 权限关系枚举/// </summary>public enum PermissionRelation{/// <summary>/// 需要同时满足/// </summary>And,/// <summary>/// 只需要满足任意一项/// </summary>Or,}
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;}}
一个接口可以声明多个特性,特性与特性之间是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;}//如果需要权限验证则必须是已登录用户,否则返回401if (_currentUser.Id == null){context.Response.StatusCode = 401;return;}//获取用户权限var userPermisions = (await _service.GetUserPermissionCode((Guid) _currentUser.Id)).ToHashSet();//比对权限 如果无权限则返回403foreach (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);}}
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))]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/"},
app.UseCusPermissions<PermissionLab>("Store");/// <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项目中将身份管理服务中角色权限相关接口封装并暴露给客户端调用,其中注册权限接口仅为聚合服务层注册权限使用,不建议暴露给客户端。
这里我只简单使用了对权限码自身的校验,并未做父子关系的关联校验,在实际项目中,可以依据需要进行修改或扩展。