namespace Admin.NET.Core; /// /// 当前用户 PC 菜单访问判定。 /// public interface ICurrentUserMenuAccessService { /// /// 判断当前 JWT 用户是否拥有指定 PC 菜单 path。 /// Task HasMenuPathAsync(string menuPath); } /// /// 要求当前用户拥有指定 PC 菜单 path。 /// /// /// 认证缺失返回 401;权限服务缺失、判定异常或无权限均返回 403(fail-closed)。 /// 超级管理员和系统管理员的豁免口径由 /// 按登录菜单树规则统一处理。 /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public sealed class RequirePcMenuPathAttribute : Attribute, IAsyncAuthorizationFilter { /// /// 要求具备其中任意一个的 PC 菜单 path。 /// public IReadOnlyList MenuPaths { get; } /// 兼容单 path 调用方;多 path 时返回首项。 public string MenuPath => MenuPaths[0]; public RequirePcMenuPathAttribute(params string[] menuPaths) { if (menuPaths == null || menuPaths.Length == 0 || menuPaths.Any(string.IsNullOrWhiteSpace)) throw new ArgumentException("PC 菜单 path 不能为空", nameof(menuPaths)); MenuPaths = menuPaths; } public async Task OnAuthorizationAsync(AuthorizationFilterContext context) { if (context.HttpContext.User?.Identity?.IsAuthenticated != true) { context.Result = new UnauthorizedResult(); return; } var accessService = context.HttpContext.RequestServices.GetService(); if (accessService == null) { context.Result = Forbid(); return; } try { foreach (var menuPath in MenuPaths) { if (await accessService.HasMenuPathAsync(menuPath)) return; } } catch { // 权限基础设施异常时不得放行受保护端点。 } context.Result = Forbid(); } private static ObjectResult Forbid() => new(new { message = "没有权限访问该功能" }) { StatusCode = StatusCodes.Status403Forbidden }; }