RequirePcMenuPathAttribute.cs 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. namespace Admin.NET.Core;
  2. /// <summary>
  3. /// 当前用户 PC 菜单访问判定。
  4. /// </summary>
  5. public interface ICurrentUserMenuAccessService
  6. {
  7. /// <summary>
  8. /// 判断当前 JWT 用户是否拥有指定 PC 菜单 path。
  9. /// </summary>
  10. Task<bool> HasMenuPathAsync(string menuPath);
  11. }
  12. /// <summary>
  13. /// 要求当前用户拥有指定 PC 菜单 path。
  14. /// </summary>
  15. /// <remarks>
  16. /// 认证缺失返回 401;权限服务缺失、判定异常或无权限均返回 403(fail-closed)。
  17. /// 超级管理员和系统管理员的豁免口径由 <see cref="ICurrentUserMenuAccessService"/>
  18. /// 按登录菜单树规则统一处理。
  19. /// </remarks>
  20. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
  21. public sealed class RequirePcMenuPathAttribute : Attribute, IAsyncAuthorizationFilter
  22. {
  23. /// <summary>
  24. /// 要求具备其中任意一个的 PC 菜单 path。
  25. /// </summary>
  26. public IReadOnlyList<string> MenuPaths { get; }
  27. /// <summary>兼容单 path 调用方;多 path 时返回首项。</summary>
  28. public string MenuPath => MenuPaths[0];
  29. public RequirePcMenuPathAttribute(params string[] menuPaths)
  30. {
  31. if (menuPaths == null || menuPaths.Length == 0 || menuPaths.Any(string.IsNullOrWhiteSpace))
  32. throw new ArgumentException("PC 菜单 path 不能为空", nameof(menuPaths));
  33. MenuPaths = menuPaths;
  34. }
  35. public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
  36. {
  37. if (context.HttpContext.User?.Identity?.IsAuthenticated != true)
  38. {
  39. context.Result = new UnauthorizedResult();
  40. return;
  41. }
  42. var accessService = context.HttpContext.RequestServices.GetService<ICurrentUserMenuAccessService>();
  43. if (accessService == null)
  44. {
  45. context.Result = Forbid();
  46. return;
  47. }
  48. try
  49. {
  50. foreach (var menuPath in MenuPaths)
  51. {
  52. if (await accessService.HasMenuPathAsync(menuPath)) return;
  53. }
  54. }
  55. catch
  56. {
  57. // 权限基础设施异常时不得放行受保护端点。
  58. }
  59. context.Result = Forbid();
  60. }
  61. private static ObjectResult Forbid() => new(new { message = "没有权限访问该功能" })
  62. {
  63. StatusCode = StatusCodes.Status403Forbidden
  64. };
  65. }