using SharedKernel.Constants; namespace Admin.Middlewares; public class MenuAuthorizationMiddleware(RequestDelegate next) { // 메뉴 경로 → 허용 역할 매핑 (앱 시작 시 1회 빌드) private static readonly Lazy Roles)>> MenuRoleMap = new(() => { var result = new List<(string Path, List Roles)>(); var menus = Menus.GetMenus(); CollectMenuRoles(menus, result); // 긴 경로 우선 매칭되도록 내림차순 정렬 result.Sort((a, b) => b.Path.Length.CompareTo(a.Path.Length)); return result; }); public async Task InvokeAsync(HttpContext context) { // 인증되지 않은 사용자는 Identity가 처리 (로그인 페이지로 이동) if (context.User.Identity is not { IsAuthenticated: true }) { await next(context); return; } var path = context.Request.Path.Value ?? ""; // 정적 파일, Identity, Error 페이지는 권한 체크 제외 if (ShouldSkip(path)) { await next(context); return; } // 메뉴에 등록된 경로인지 확인 var matchedRoles = FindMatchingRoles(path); if (matchedRoles != null) { // 역할 중 하나라도 가지고 있으면 허용 var hasAccess = matchedRoles.Any(role => context.User.IsInRole(role)); if (!hasAccess) { context.Response.StatusCode = 403; context.Response.Redirect("/Identity/Account/AccessDenied"); return; } } await next(context); } private static List? FindMatchingRoles(string path) { var trimmedPath = path.TrimEnd('/'); if (string.IsNullOrEmpty(trimmedPath)) { return null; } foreach (var (menuPath, roles) in MenuRoleMap.Value) { if (trimmedPath.Equals(menuPath, StringComparison.OrdinalIgnoreCase) || trimmedPath.StartsWith(menuPath + "/", StringComparison.OrdinalIgnoreCase)) { return roles; } } return null; } // 권한 체크 제외 경로 private static readonly string[] SkipPrefixes = [ "/Identity/", "/lib/", "/css/", "/js/", "/images/", "/favicon", "/_framework", "/_blazor", "/.well-known/", "/Error" ]; private static bool ShouldSkip(string path) { // 확장자가 있으면 정적 파일로 간주 var lastSegment = path.AsSpan(path.LastIndexOf('/') + 1); if (lastSegment.Contains('.')) { return true; } foreach (var prefix in SkipPrefixes) { if (path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } // 메뉴 트리에서 경로와 역할을 수집하는 재귀 메서드 private static void CollectMenuRoles(List menus, List<(string Path, List Roles)> result) { foreach (var menu in menus) { if (!string.IsNullOrEmpty(menu.Path) && menu.Roles is { Count: > 0 }) { result.Add((menu.Path.TrimEnd('/'), menu.Roles)); } if (menu.Children is { Count: > 0 }) { CollectMenuRoles(menu.Children, result); } } } }