KIM-JINO5 3 mesi fa
parent
commit
8564d13643

+ 1 - 1
Admin/Areas/Identity/Pages/Account/Login.cshtml.cs

@@ -85,7 +85,7 @@ namespace Admin.Areas.Identity.Pages.Account
             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
             ///     directly from your code. This API may change or be removed in future releases.
             /// </summary>
-            [Display(Name = "��� ���� ����")]
+            [Display(Name = "로그인 상태 유지")]
             public bool RememberMe { get; set; }
         }
 

+ 1 - 1
Admin/Areas/Identity/Pages/Account/Manage/Email.cshtml

@@ -34,7 +34,7 @@
                 <label asp-for="Input.NewEmail" class="form-label"></label>
                 <span asp-validation-for="Input.NewEmail" class="text-danger"></span>
             </div>
-            <button id="change-email-button" type="submit" asp-page-handler="ChangeEmail" class="w-100 btn btn-lg btn-primary">Change email</button>
+            <button id="change-email-button" type="submit" asp-page-handler="ChangeEmail" class="w-100 btn btn-lg btn-primary">변경하기</button>
         </form>
     </div>
 </div>

+ 5 - 1
Admin/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml

@@ -25,7 +25,11 @@
                 <span asp-validation-for="Input.Password" class="text-danger"></span>
             </div>
         }
-        <button class="btn btn-danger justify-content-center" type="submit">회원탈퇴 처리에 동의합니다.</button>
+        <div class="row">
+            <div class="col col-sm-auto">
+                <button class="btn btn-danger w-100" type="submit">회원탈퇴 처리에 동의합니다.</button>
+            </div>
+        </div>
     </form>
 </div>
 

+ 1 - 1
Admin/Areas/Identity/Pages/Account/Manage/_ManageNav.cshtml

@@ -3,7 +3,7 @@
 @{
     var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any();
 }
-<ul class="nav nav-pills flex-column">
+<ul class="nav nav-pills flex-column mb-4 mb-md-0">
     <li class="nav-item"><a class="nav-link @ManageNavPages.IndexNavClass(ViewContext)" id="profile" asp-page="./Index">기본 정보</a></li>
     <li class="nav-item"><a class="nav-link @ManageNavPages.EmailNavClass(ViewContext)" id="email" asp-page="./Email">이메일 변경</a></li>
     <li class="nav-item"><a class="nav-link @ManageNavPages.ChangePasswordNavClass(ViewContext)" id="change-password" asp-page="./ChangePassword">비밀번호 변경</a></li>

+ 1 - 1
Admin/Areas/Identity/Pages/_ValidationScriptsPartial.cshtml

@@ -1,2 +1,2 @@
 <script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
-<script src="~/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"></script>
+<script src="~/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"></script>

+ 128 - 0
Admin/Middlewares/MenuAuthorizationMiddleware.cs

@@ -0,0 +1,128 @@
+using SharedKernel.Constants;
+
+namespace Admin.Middlewares;
+
+public class MenuAuthorizationMiddleware(RequestDelegate next)
+{
+    // 메뉴 경로 → 허용 역할 매핑 (앱 시작 시 1회 빌드)
+    private static readonly Lazy<List<(string Path, List<string> Roles)>> MenuRoleMap = new(() =>
+    {
+        var result = new List<(string Path, List<string> 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<string>? 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<Menu> menus, List<(string Path, List<string> 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);
+            }
+        }
+    }
+}

+ 2 - 2
Admin/Pages/Member/List/Write.cshtml

@@ -195,9 +195,9 @@
             </div>
         </div>
         <div class="row mb-2">
-            <label asp-for="Input.YouTubeHandle" class="col-sm-2 col-form-label"></label>
+            <label asp-for="Input.YouTubeHandle" class="col-sm-2 col-form-label">핸들</label>
             <div class="col-sm-10">
-                <div class="input-group mb-3">
+                <div class="input-group">
                     <span class="input-group-text">@@</span>
                     <input type="text" asp-for="Input.YouTubeHandle" class="form-control" maxlength="30" />
                 </div>

+ 3 - 0
Admin/Program.cs

@@ -80,6 +80,9 @@ app.UseRouting();
 app.UseAuthentication();
 app.UseAuthorization();
 
+// 메뉴 기반 역할 권한 체크
+app.UseMiddleware<MenuAuthorizationMiddleware>();
+
 // 관리자 접속 기록
 app.UseMiddleware<AdminAccessLogMiddleware>();