From 51019d7f5fb1befc9c9cdcd3c13be47367d8a429 Mon Sep 17 00:00:00 2001 From: sohot8653 Date: Thu, 25 Sep 2025 11:28:12 +0900 Subject: [PATCH] =?UTF-8?q?[=EB=AF=B8=EB=93=A4=EC=9B=A8=EC=96=B4=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B6=8C=ED=95=9C=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0]=20=EA=B8=B0=EC=A1=B4=20auth=20=EB=AF=B8?= =?UTF-8?q?=EB=93=A4=EC=9B=A8=EC=96=B4=EB=A5=BC=20auth.global.ts=EB=A1=9C?= =?UTF-8?q?=20=ED=86=B5=ED=95=A9=ED=95=98=EC=97=AC=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EB=B0=8F=20=EA=B6=8C=ED=95=9C=20=EC=B2=B4=ED=81=AC?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=EA=B0=9C=EC=84=A0=ED=95=98?= =?UTF-8?q?=EA=B3=A0,=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B6=8C=ED=95=9C?= =?UTF-8?q?=20=ED=99=95=EC=9D=B8=20=EB=B0=A9=EC=8B=9D=EC=9D=84=20=EB=8F=99?= =?UTF-8?q?=EC=A0=81=20=EB=9D=BC=EC=9A=B0=ED=8C=85=20=ED=8C=A8=ED=84=B4?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=ED=99=95=EC=9E=A5=ED=95=A8.=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20=EA=B0=84=EC=86=8C?= =?UTF-8?q?=ED=99=94.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- layouts/default.vue | 2 -- middleware/auth.global.ts | 32 ++++++++++++++++++++++++++++++++ middleware/auth.ts | 30 ------------------------------ stores/permissions.ts | 18 +++++++++++++++++- stores/tab.ts | 1 - stores/user.ts | 28 ++++++++++------------------ 6 files changed, 59 insertions(+), 52 deletions(-) create mode 100644 middleware/auth.global.ts delete mode 100644 middleware/auth.ts diff --git a/layouts/default.vue b/layouts/default.vue index a820aab..40cd245 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -23,11 +23,9 @@ import AppHeader from "../components/layout/AppHeader.vue"; import SubMenuBar from "../components/layout/SubMenuBar.vue"; import TabBar from "../components/layout/TabBar.vue"; import { ref, computed } from "vue"; -import { useRouter } from "vue-router"; import { useTabsStore } from "../stores/tab"; import { usePermissionsStore } from "~/stores/permissions"; -const router = useRouter(); const activeMenu = ref("HOME"); const showSubmenuBar = ref(false); diff --git a/middleware/auth.global.ts b/middleware/auth.global.ts new file mode 100644 index 0000000..d1a8c24 --- /dev/null +++ b/middleware/auth.global.ts @@ -0,0 +1,32 @@ +export default defineNuxtRouteMiddleware(async (to, _from) => { + if (import.meta.client) { + const userStore = useUserStore(); + const { hasPagePermission } = usePermission(); + + // 공개 라우트 목록 (로그인 없이 접근 가능) + const publicRoutes = ["/login", "/register"]; + + // 공개 라우트인지 확인 + const isPublicRoute = publicRoutes.some(route => to.path === route); + + // 공개 라우트가 아닌 경우 로그인 체크 + if (!isPublicRoute && !userStore.isLoggedIn) { + return navigateTo("/login"); + } + + // 로그인된 사용자의 경우 권한 체크 + if (userStore.isLoggedIn) { + // 루트 경로는 항상 허용 + if (to.path === "/") { + return; + } + + // 페이지 권한 체크 + if (!hasPagePermission(to.path)) { + console.log(`페이지 권한이 없습니다.: ${to.path}`); + alert(`페이지 권한이 없습니다.`); + return navigateTo("/"); + } + } + } +}); diff --git a/middleware/auth.ts b/middleware/auth.ts deleted file mode 100644 index 5ca6281..0000000 --- a/middleware/auth.ts +++ /dev/null @@ -1,30 +0,0 @@ -export default defineNuxtRouteMiddleware((to, _from) => { - // 클라이언트 사이드에서만 실행 - if (import.meta.client) { - const userStore = useUserStore(); - const permissionsStore = usePermissionsStore(); - - // 보호된 라우트 목록(메뉴 확정되면 수정) - const protectedRoutes = ["/admin", "/profile", "/dashboard"]; - - // 현재 라우트가 보호된 라우트인지 확인 - const isProtectedRoute = protectedRoutes.some(route => - to.path.startsWith(route) - ); - - // 로그인 체크 - if (isProtectedRoute && !userStore.isLoggedIn) { - // 인증되지 않은 사용자를 로그인 페이지로 리다이렉트 - return navigateTo("/login"); - } - - // API 권한 체크 (로그인된 사용자만) - if (userStore.isLoggedIn) { - const currentPath = to.path; - if (!permissionsStore.hasApiPermission(currentPath)) { - // 권한이 없는 경우 홈으로 리다이렉트 - return navigateTo("/"); - } - } - } -}); diff --git a/stores/permissions.ts b/stores/permissions.ts index cf46c00..bc7b7f2 100644 --- a/stores/permissions.ts +++ b/stores/permissions.ts @@ -72,7 +72,23 @@ export const usePermissionsStore = defineStore( }; const hasPagePermission = (page: string): boolean => { - return getPagePaths().includes(page); + const pagePaths = getPagePaths(); + + // 1. 정확한 경로 매치 먼저 확인 + if (pagePaths.includes(page)) { + return true; + } + + // 2. 동적 라우팅 패턴 체크 + for (const allowedPath of pagePaths) { + // 동적 라우팅 패턴: /[tabId]/path 형태 + const dynamicPattern = new RegExp(`^/\\d+${allowedPath}$`); + if (dynamicPattern.test(page)) { + return true; + } + } + + return false; }; const hasPageGroupPermission = (pageGroup: string): boolean => { diff --git a/stores/tab.ts b/stores/tab.ts index a539652..6e0e8f4 100644 --- a/stores/tab.ts +++ b/stores/tab.ts @@ -15,7 +15,6 @@ export const useTabsStore = defineStore("tabs", { activeTab: 1, }), actions: { - // 서브메뉴 클릭 시 새 탭 생성 (중복 체크 추가) async updateActiveTab(sub: { label: string; to: string; diff --git a/stores/user.ts b/stores/user.ts index bde4788..833296d 100644 --- a/stores/user.ts +++ b/stores/user.ts @@ -48,27 +48,19 @@ export const useUserStore = defineStore( }; const logout = async () => { - try { - const response = await useApi("/members/logout", { - method: "post", - loadingMessage: "로그아웃 처리중...", - handleError: false, - showAlert: false, - }); + await useApi("/members/logout", { + method: "post", + loadingMessage: "로그아웃 처리중...", + showAlert: false, + }); - console.log("response:", response); - } catch (error) { - console.error(error); - } finally { - user.value = null; - isLoggedIn.value = false; + user.value = null; + isLoggedIn.value = false; - tabsStore.resetTabs(); - permissionsStore.clearPermissions(); + tabsStore.resetTabs(); + permissionsStore.clearPermissions(); - // 로그인 페이지로 이동 - await navigateTo("/login"); - } + await navigateTo("/login"); }; return {