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
};
}