2025๋…„ 11์›” 30์ผยท3๋ถ„ ์ฝ๊ธฐ

Vue 3 ์—ญํ•  ๊ธฐ๋ฐ˜ ์ ‘๊ทผ์„ ์œ„ํ•œ ๋ผ์šฐํŒ… ๊ฐ€๋“œ: ์‹ค์ „ ํŒจํ„ด

Vue 3 ๋ผ์šฐํŒ… ๊ฐ€๋“œ๋ฅผ ํ†ตํ•œ ์—ญํ•  ๊ธฐ๋ฐ˜ ์ ‘๊ทผ์„ ์‹ค์ „ ํŒจํ„ด์œผ๋กœ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค: ๋ผ์šฐํŠธ meta ๊ทœ์น™, ์•ˆ์ „ํ•œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ, ์นœ์ ˆํ•œ 401/403 ํด๋ฐฑ, ๋ฐ์ดํ„ฐ ์œ ์ถœ ๋ฐฉ์ง€ ๋ฐฉ๋ฒ• ํฌํ•จ.

Vue 3 ์—ญํ•  ๊ธฐ๋ฐ˜ ์ ‘๊ทผ์„ ์œ„ํ•œ ๋ผ์šฐํŒ… ๊ฐ€๋“œ: ์‹ค์ „ ํŒจํ„ด

๋ผ์šฐํŠธ ๊ฐ€๋“œ๊ฐ€ ์‹ค์ œ๋กœ ํ•ด๊ฒฐํ•˜๋Š” ๊ฒƒ(๊ทธ๋ฆฌ๊ณ  ํ•ด๊ฒฐํ•˜์ง€ ๋ชปํ•˜๋Š” ๊ฒƒ)\n\n๋ผ์šฐํŠธ ๊ฐ€๋“œ๋Š” ํ•œ ๊ฐ€์ง€ ์ผ์„ ์ž˜ํ•ฉ๋‹ˆ๋‹ค: ๋„ค๋น„๊ฒŒ์ด์…˜์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. ๋ˆ„๊ฐ€ ๊ฒฝ๋กœ์— ์ง„์ž…ํ•  ์ˆ˜ ์žˆ๋Š”์ง€, ์ง„์ž…ํ•  ์ˆ˜ ์—†์„ ๋•Œ ์–ด๋””๋กœ ๋ณด๋‚ผ์ง€ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” UX๋ฅผ ๊ฐœ์„ ํ•˜์ง€๋งŒ ๋ณด์•ˆ๊ณผ๋Š” ๋ณ„๊ฐœ์ž…๋‹ˆ๋‹ค.\n\n๋ฉ”๋‰ด ํ•ญ๋ชฉ์„ ์ˆจ๊ธฐ๋Š” ๊ฒƒ์€ ํžŒํŠธ์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. ์‚ฌ๋žŒ๋“ค์€ ์—ฌ์ „ํžˆ URL์„ ์ž…๋ ฅํ•˜๊ฑฐ๋‚˜, ๋”ฅ ๋งํฌ์—์„œ ์ƒˆ๋กœ๊ณ ์นจํ•˜๊ฑฐ๋‚˜, ์ฆ๊ฒจ์ฐพ๊ธฐ๋ฅผ ์—ด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์œ ์ผํ•œ ๋ณดํ˜ธ๊ฐ€ "๋ฒ„ํŠผ์ด ๋ณด์ด์ง€ ์•Š๋Š”๋‹ค"๋ผ๋ฉด, ๋ณดํ˜ธ๊ฐ€ ์—†๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.\n\n๊ฐ€๋“œ๋Š” ๊ด€๋ฆฌ์ž ์˜์—ญ, ๋‚ด๋ถ€ ๋„๊ตฌ, ์—ญํ•  ๊ธฐ๋ฐ˜ ๊ณ ๊ฐ ํฌํ„ธ์ฒ˜๋Ÿผ ๋ณด์—ฌ์„œ๋Š” ์•ˆ ๋˜๋Š” ํŽ˜์ด์ง€๋ฅผ ์ฐจ๋‹จํ•˜๋ฉด์„œ ์•ฑ์ด ์ผ๊ด€๋˜๊ฒŒ ๋™์ž‘ํ•˜๋„๋ก ํ•  ๋•Œ ๋น›์„ ๋ฐœํ•ฉ๋‹ˆ๋‹ค.\n\n๊ฐ€๋“œ๋ฅผ ํ†ตํ•ด ์–ป๋Š” ์ด์ :\n\n- ํŽ˜์ด์ง€๊ฐ€ ๋ Œ๋”๋ง๋˜๊ธฐ ์ „์— ์ฐจ๋‹จ\n- ๋กœ๊ทธ์ธ์ด๋‚˜ ์•ˆ์ „ํ•œ ๊ธฐ๋ณธ ๊ฒฝ๋กœ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ\n- ๊นจ์ง„ ๋ทฐ ๋Œ€์‹  ๋ช…ํ™•ํ•œ 401/403 ํ™”๋ฉด ์ œ๊ณต\n- ์šฐ๋ฐœ์ ์ธ ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฃจํ”„ ํšŒํ”ผ\n\n๊ฐ€๋“œ๋งŒ์œผ๋กœ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ณดํ˜ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. API๊ฐ€ ๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ธŒ๋ผ์šฐ์ €๋กœ ๋ฐ˜ํ™˜ํ•˜๋ฉด, ํŽ˜์ด์ง€๊ฐ€ ์ฐจ๋‹จ๋˜๋”๋ผ๋„ ์‚ฌ์šฉ์ž๋Š” ๊ทธ ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜๊ฑฐ๋‚˜ ๊ฐœ๋ฐœ์ž ๋„๊ตฌ๋กœ ์‘๋‹ต์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‹ค์ œ ๊ถŒํ•œ ๊ฒ€์‚ฌ๋Š” ์„œ๋ฒ„์—์„œ๋„ ์ด๋ค„์ ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n\n์ข‹์€ ๋ชฉํ‘œ๋Š” ์–‘์ชฝ์„ ๋ชจ๋‘ ์ปค๋ฒ„ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค: ํŽ˜์ด์ง€๋ฅผ ์ฐจ๋‹จํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ์ฐจ๋‹จํ•˜์„ธ์š”. ์ง€์› ๋‹ด๋‹น์ž๊ฐ€ ๊ด€๋ฆฌ์ž ์ „์šฉ ๊ฒฝ๋กœ๋ฅผ ์—ด๋ฉด ๊ฐ€๋“œ๋Š” ๋„ค๋น„๊ฒŒ์ด์…˜์„ ์ค‘๋‹จํ•˜๊ณ  "Access denied"๋ฅผ ๋ณด์—ฌ์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ณ„๋„๋กœ ๋ฐฑ์—”๋“œ๋Š” ๊ด€๋ฆฌ์ž ์ „์šฉ API ํ˜ธ์ถœ์„ ๊ฑฐ๋ถ€ํ•ด ์ œํ•œ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ ˆ๋Œ€ ๋ฐ˜ํ™˜๋˜์ง€ ์•Š๋„๋ก ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n\n## ๊ฐ„๋‹จํ•œ ์—ญํ•  ๋ฐ ๊ถŒํ•œ ๋ชจ๋ธ ์„ ํƒ\n\n์—ญํ•  ๋ชฉ๋ก์ด ๊ธธ์–ด์ง€๋ฉด ์ ‘๊ทผ ์ œ์–ด๊ฐ€ ๋ณต์žกํ•ด์ง‘๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ ์‚ฌ๋žŒ๋“ค์ด ์ดํ•ดํ•˜๋Š” ์ž‘์€ ์ง‘ํ•ฉ์œผ๋กœ ์‹œ์ž‘ํ•˜๊ณ , ์ง„์งœ๋กœ ํ•„์š”ํ•  ๋•Œ๋งŒ ์„ธ๋ถ€ ๊ถŒํ•œ์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”.\n\n์‹ค์šฉ์ ์ธ ๋ถ„๋ฆฌ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:\n\n- ์—ญํ• (Roles)์€ ์•ฑ์—์„œ ๋ˆ„๊ตฐ์ง€๋ฅผ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.\n- ๊ถŒํ•œ(Permissions)์€ ๋ฌด์—‡์„ ํ•  ์ˆ˜ ์žˆ๋Š”์ง€๋ฅผ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.\n\n๋Œ€๋ถ€๋ถ„์˜ ๋‚ด๋ถ€ ๋„๊ตฌ์—์„œ๋Š” ์„ธ ๊ฐ€์ง€ ์—ญํ• ์ด๋ฉด ์ถฉ๋ถ„ํ•œ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค:\n\n- admin: ์‚ฌ์šฉ์ž ๋ฐ ์„ค์ • ๊ด€๋ฆฌ, ๋ชจ๋“  ๋ฐ์ดํ„ฐ ํ™•์ธ\n- support: ๊ณ ๊ฐ ๊ธฐ๋ก๊ณผ ์‘๋‹ต ์ฒ˜๋ฆฌ, ์‹œ์Šคํ…œ ์„ค์ •์€ ์•„๋‹˜\n- viewer: ์Šน์ธ๋œ ํ™”๋ฉด์— ๋Œ€ํ•œ ์ฝ๊ธฐ ์ „์šฉ ์ ‘๊ทผ\n\n์—ญํ• ์ด ์–ด๋””์„œ ์˜ค๋Š”์ง€ ์ผ์ฐ ๊ฒฐ์ •ํ•˜์„ธ์š”. ํ† ํฐ ํด๋ ˆ์ž„(JWT)์€ ๊ฐ€๋“œ์— ๋น ๋ฅด์ง€๋งŒ ๊ฐฑ์‹ ๋  ๋•Œ๊นŒ์ง€ ์˜ค๋ž˜๋œ ์ƒํƒœ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•ฑ ์‹œ์ž‘ ์‹œ ์‚ฌ์šฉ์ž ํ”„๋กœํ•„์„ ๊ฐ€์ ธ์˜ค๋ฉด ํ•ญ์ƒ ์ตœ์‹ ์ด์ง€๋งŒ ๊ฐ€๋“œ๊ฐ€ ๊ทธ ์š”์ฒญ์ด ๋๋‚  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ค์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n\n๋˜ํ•œ ๋ผ์šฐํŠธ ์œ ํ˜•์„ ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌํ•˜์„ธ์š”: ๊ณต๊ฐœ ๋ผ์šฐํŠธ(๋ชจ๋‘ ์ ‘๊ทผ), ์ธ์ฆ๋œ ๋ผ์šฐํŠธ(์„ธ์…˜ ํ•„์š”), ์ œํ•œ๋œ ๋ผ์šฐํŠธ(์—ญํ•  ๋˜๋Š” ๊ถŒํ•œ ํ•„์š”).\n\n## ๋ผ์šฐํŠธ meta๋กœ ์ ‘๊ทผ ๊ทœ์น™ ์ •์˜\n\n์ ‘๊ทผ์„ ํ‘œํ˜„ํ•˜๋Š” ๊ฐ€์žฅ ๊น”๋”ํ•œ ๋ฐฉ๋ฒ•์€ ๋ผ์šฐํŠธ ์ž์ฒด์— ์„ ์–ธํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. Vue Router๋Š” ๊ฐ ๋ผ์šฐํŠธ ๋ ˆ์ฝ”๋“œ์— meta ๊ฐ์ฒด๋ฅผ ๋ถ™์ผ ์ˆ˜ ์žˆ์–ด ๊ฐ€๋“œ๊ฐ€ ๋‚˜์ค‘์— ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๊ทœ์น™์„ ๋ณดํ˜ธํ•  ํŽ˜์ด์ง€ ๊ฐ€๊นŒ์ด์— ๋‘์–ด ๊ด€๋ฆฌํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค.\n\n๋‹จ์ˆœํ•œ meta ํ˜•ํƒœ๋ฅผ ์„ ํƒํ•˜๊ณ  ์•ฑ ์ „์ฒด์—์„œ ์ผ๊ด€๋˜๊ฒŒ ์‚ฌ์šฉํ•˜์„ธ์š”.\n\njs\nconst routes = [\n {\n path: "/admin",\n component: () => import("@/pages/AdminLayout.vue"),\n meta: { requiresAuth: true, roles: ["admin"] },\n children: [\n {\n path: "users",\n component: () => import("@/pages/AdminUsers.vue"),\n // inherits requiresAuth + roles from parent\n },\n {\n path: "audit",\n component: () => import("@/pages/AdminAudit.vue"),\n meta: { permissions: ["audit:read"] },\n },\n ],\n },\n {\n path: "/tickets",\n component: () => import("@/pages/Tickets.vue"),\n meta: { requiresAuth: true, permissions: ["tickets:read"], readOnly: true },\n },\n]\n\n\n์ค‘์ฒฉ ๋ผ์šฐํŠธ์˜ ๊ฒฝ์šฐ ๊ทœ์น™์„ ์–ด๋–ป๊ฒŒ ๊ฒฐํ•ฉํ• ์ง€ ๊ฒฐ์ •ํ•˜์„ธ์š”. ๋Œ€๋ถ€๋ถ„์˜ ์•ฑ์—์„œ๋Š” ์ž์‹์ด ๋ถ€๋ชจ ์š”๊ตฌ์‚ฌํ•ญ์„ ์ƒ์†ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ€๋“œ์—์„œ๋Š” ๋งค์น˜๋œ ๋ชจ๋“  ๋ผ์šฐํŠธ ๋ ˆ์ฝ”๋“œ(๋‹จ์ˆœํžˆ to.meta๋งŒ์ด ์•„๋‹ˆ๋ผ)๋ฅผ ๊ฒ€์‚ฌํ•ด ๋ถ€๋ชจ ๊ทœ์น™์ด ๊ฑด๋„ˆ๋›ฐ์–ด์ง€์ง€ ์•Š๋„๋ก ํ•˜์„ธ์š”.\n\n๋‚˜์ค‘์— ์‹œ๊ฐ„์„ ์ ˆ์•ฝํ•ด์ฃผ๋Š” ํ•œ ๊ฐ€์ง€ ์„ธ๋ถ€์‚ฌํ•ญ: "์กฐํšŒ ๊ฐ€๋Šฅ(can view)"๊ณผ "์ˆ˜์ • ๊ฐ€๋Šฅ(can edit)"์„ ๊ตฌ๋ถ„ํ•˜์„ธ์š”. ์–ด๋–ค ๋ผ์šฐํŠธ๋Š” support์™€ admin์ด ๋ณผ ์ˆ˜ ์žˆ์ง€๋งŒ, support๋Š” ์ˆ˜์ •์„ ๋น„ํ™œ์„ฑํ™”ํ•ด์•ผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. meta์— readOnly: true ํ”Œ๋ž˜๊ทธ๋ฅผ ๋‘๋ฉด UI ๋™์ž‘(์•ก์…˜ ๋น„ํ™œ์„ฑํ™”, ํŒŒ๊ดด์  ๋ฒ„ํŠผ ์ˆจ๊ธฐ๊ธฐ)์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๊ณ , ์ด๋ฅผ ๋ณด์•ˆ์œผ๋กœ ์˜คํ•ดํ•˜์ง€ ์•Š๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.\n\n## ๊ฐ€๋“œ๊ฐ€ ์‹ ๋ขฐ์„ฑ ์žˆ๊ฒŒ ๋™์ž‘ํ•˜๋„๋ก ์ธ์ฆ ์ƒํƒœ ์ค€๋น„\n\n๊ฐ€๋“œ ๋ฒ„๊ทธ์˜ ๋Œ€๋ถ€๋ถ„์€ ํ•œ ๊ฐ€์ง€ ๋ฌธ์ œ์—์„œ ์˜ต๋‹ˆ๋‹ค: ๊ฐ€๋“œ๊ฐ€ ์•ฑ์ด ์‚ฌ์šฉ์ž๋ฅผ ์•Œ๊ธฐ ์ „์— ์‹คํ–‰๋˜๋Š” ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค.\n\n์ธ์ฆ์„ ์ž‘์€ ์ƒํƒœ ๋จธ์‹ ์ฒ˜๋Ÿผ ์ทจ๊ธ‰ํ•˜๊ณ  ๊ทธ๊ฒƒ์„ ๋‹จ์ผ ์‹ ๋ขฐ ์†Œ์Šค๋กœ ๋งŒ๋“œ์„ธ์š”. ์„ธ ๊ฐ€์ง€ ๋ช…ํ™•ํ•œ ์ƒํƒœ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค:\n\n- unknown: ์•ฑ์ด ๋ง‰ ์‹œ์ž‘ํ–ˆ๊ณ  ์„ธ์…˜์ด ์•„์ง ํ™•์ธ๋˜์ง€ ์•Š์Œ\n- logged out: ์„ธ์…˜ ํ™•์ธ์ด ๋๋‚ฌ๊ณ  ์œ ํšจํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ์—†์Œ\n- logged in: ์‚ฌ์šฉ์ž ๋กœ๋“œ ์™„๋ฃŒ, ์—ญํ• /๊ถŒํ•œ ์ด์šฉ ๊ฐ€๋Šฅ\n\n๊ทœ์น™: ์ธ์ฆ์ด unknown์ผ ๋•Œ ์ ˆ๋Œ€ ์—ญํ• ์„ ์ฝ์ง€ ๋งˆ์„ธ์š”. ์ด๊ฒƒ์ด ๋ณดํ˜ธ๋œ ํ™”๋ฉด์˜ ํ”Œ๋ž˜์‹œ๋‚˜ ๋†€๋ผ์šด ๋กœ๊ทธ์ธ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋ฅผ ๋งŒ๋“œ๋Š” ์›์ธ์ž…๋‹ˆ๋‹ค.\n\n### ์„ธ์…˜ ๊ฐฑ์‹  ๋ฐฉ์‹ ๊ฒฐ์ •\n\nํ•˜๋‚˜์˜ ๊ฐฑ์‹  ์ „๋žต์„ ์ •ํ•˜๊ณ  ์˜ˆ์ธก ๊ฐ€๋Šฅํ•˜๊ฒŒ ์œ ์ง€ํ•˜์„ธ์š”(์˜ˆ: ํ† ํฐ ์ฝ๊ธฐ, who am I ์—”๋“œํฌ์ธํŠธ ํ˜ธ์ถœ, ์‚ฌ์šฉ์ž ์„ค์ •).\n\n์•ˆ์ •์ ์ธ ํŒจํ„ด ์˜ˆ์‹œ:\n\n- ์•ฑ ๋กœ๋“œ ์‹œ ์ธ์ฆ์„ unknown์œผ๋กœ ์„ค์ •ํ•˜๊ณ  ๋‹จ์ผ ๊ฐฑ์‹  ์š”์ฒญ์„ ์‹œ์ž‘\n- ๊ฐฑ์‹ ์ด ๋๋‚˜๊ฑฐ๋‚˜ ํƒ€์ž„์•„์›ƒ๋  ๋•Œ๊นŒ์ง€ ๊ฐ€๋“œ๋Š” ํ•ด์ œํ•˜์ง€ ์•Š์Œ\n- ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋ฉ”ํƒ€ ๋Œ€์‹  ๋ฉ”๋ชจ๋ฆฌ์— ์บ์‹œ\n- ์‹คํŒจ ์‹œ ์ธ์ฆ์„ logged out์œผ๋กœ ์„ค์ •\n- ๊ฐ€๋“œ๊ฐ€ ๋Œ€๊ธฐํ•  ์ˆ˜ ์žˆ๋Š” ready ํ”„๋ผ๋ฏธ์Šค(๋˜๋Š” ์œ ์‚ฌ)๋ฅผ ๋…ธ์ถœ\n\n์ด๊ฒŒ ๋งˆ๋ จ๋˜๋ฉด ๊ฐ€๋“œ ๋กœ์ง์€ ๋‹จ์ˆœํ•ด์ง‘๋‹ˆ๋‹ค: auth๊ฐ€ ์ค€๋น„๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ธ๋‹ค๊ฐ€ ์ ‘๊ทผ์„ ๊ฒฐ์ •ํ•˜์„ธ์š”.\n\n## ๋‹จ๊ณ„๋ณ„: ๋ผ์šฐํŠธ ์ˆ˜์ค€ ๊ถŒํ•œ ๊ตฌํ˜„\n\n๊น”๋”ํ•œ ์ ‘๊ทผ๋ฒ•์€ ๋Œ€๋ถ€๋ถ„์˜ ๊ทœ์น™์„ ํ•˜๋‚˜์˜ ์ „์—ญ ๊ฐ€๋“œ์— ๋‘๊ณ , ๋ผ์šฐํŠธ๋ณ„๋กœ ์ง„์งœ๋กœ ํŠน๋ณ„ํ•œ ๋กœ์ง์ด ํ•„์š”ํ•  ๋•Œ๋งŒ beforeEnter๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.\n\n### 1) ์ „์—ญ beforeEach ๊ฐ€๋“œ ์ถ”๊ฐ€\n\njs\n// router/index.js\nrouter.beforeEach(async (to) => {\n const auth = useAuthStore()\n\n // Step 2: wait for auth initialization when needed\n if (!auth.ready) await auth.init()\n\n // Step 3: check authentication, then roles/permissions\n if (to.meta.requiresAuth && !auth.isAuthenticated) {\n return { name: 'login', query: { redirect: to.fullPath } }\n }\n\n const roles = to.meta.roles\n if (roles && roles.length > 0 && !roles.includes(auth.userRole)) {\n return { name: 'forbidden' } // 403\n }\n\n // Step 4: allow navigation\n return true\n})\n\n\n์ด ํŒจํ„ด์€ ์ปดํฌ๋„ŒํŠธ ์ „์—ญ์— ์ฒดํฌ๋ฅผ ํฉ์–ด๋†“์ง€ ์•Š๊ณ  ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.\n\n### beforeEnter๊ฐ€ ๋” ์ ํ•ฉํ•œ ๊ฒฝ์šฐ\n\nbeforeEnter๋Š” ๊ทœ์น™์ด ์ง„์งœ๋กœ ๋ผ์šฐํŠธ๋ณ„์ด๊ณ  to.params.id์— ์˜์กดํ•  ๋•Œ ์‚ฌ์šฉํ•˜์„ธ์š”(์˜ˆ: "ํ‹ฐ์ผ“ ์†Œ์œ ์ž๋งŒ ์—ด ์ˆ˜ ์žˆ๋‹ค"). ์งง๊ฒŒ ์œ ์ง€ํ•˜๊ณ  ๋™์ผํ•œ ์ธ์ฆ ์Šคํ† ์–ด๋ฅผ ์žฌ์‚ฌ์šฉํ•ด ๋™์ž‘์„ ์ผ๊ด€๋˜๊ฒŒ ๋งŒ๋“œ์„ธ์š”.\n\n## ํ™€์„ ๋งŒ๋“ค์ง€ ์•Š๋Š” ์•ˆ์ „ํ•œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ\n\n๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋Š” ์‹ ๋ขฐํ•˜๋ฉด ์ ‘๊ทผ ์ œ์–ด๋ฅผ ์กฐ์šฉํžˆ ๋ฌด๋ ฅํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\n\n์ผ๋ฐ˜ ํŒจํ„ด์€: ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์•„์›ƒ ์ƒํƒœ๋ฉด ๋กœ๊ทธ์ธ์œผ๋กœ ๋ณด๋‚ด๊ณ  returnTo ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ์— ์›๋ž˜ ๊ฒฝ๋กœ๋ฅผ ๋„ฃ์Šต๋‹ˆ๋‹ค. ๋กœ๊ทธ์ธ ํ›„ ์ด๋ฅผ ์ฝ์–ด ๋˜๋Œ์•„๊ฐ€๊ฒŒ ํ•˜์ฃ . ์œ„ํ—˜์€ ์˜คํ”ˆ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ(์˜๋„ํ•˜์ง€ ์•Š์€ ์™ธ๋ถ€๋กœ ๋ณด๋‚ด๊ธฐ)์™€ ๋ฃจํ”„์ž…๋‹ˆ๋‹ค.\n\nํ–‰๋™์„ ๋‹จ์ˆœํ•˜๊ฒŒ ์œ ์ง€ํ•˜์„ธ์š”:\n\n- ๋กœ๊ทธ์•„์›ƒ ์‚ฌ์šฉ์ž๋Š” ํ˜„์žฌ ๊ฒฝ๋กœ๋ฅผ returnTo๋กœ ์„ค์ •ํ•ด Login์œผ๋กœ ์ด๋™\n- ๋กœ๊ทธ์ธํ–ˆ์ง€๋งŒ ๊ถŒํ•œ์ด ์—†๋Š” ์‚ฌ์šฉ์ž๋Š” ์ „์šฉ Forbidden ํŽ˜์ด์ง€๋กœ ๋ณด๋ƒ„(๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ์•„๋‹˜)\n- ํ—ˆ์šฉ๋œ ๋‚ด๋ถ€ returnTo ๊ฐ’๋งŒ ํ—ˆ์šฉ\n- ๊ฐ™์€ ๊ณณ์œผ๋กœ ๊ณ„์† ๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•˜๋Š” ๋ฃจํ”„ ์ฒดํฌ ์ถ”๊ฐ€\n\njs\nconst allowedReturnTo = (to) => {\n if (!to || typeof to !== 'string') return null\n if (!to.startsWith('/')) return null\n // optional: only allow known prefixes\n if (!['/app', '/admin', '/tickets'].some(p => to.startsWith(p))) return null\n return to\n}\n\nrouter.beforeEach((to) => {\n if (!auth.isReady) return false\n\n if (!auth.isLoggedIn && to.name !== 'Login') {\n return { name: 'Login', query: { returnTo: to.fullPath } }\n }\n\n if (auth.isLoggedIn && !canAccess(to, auth.user) && to.name !== 'Forbidden') {\n return { name: 'Forbidden' }\n }\n})\n\n\n## ๋„ค๋น„๊ฒŒ์ด์…˜ ์ค‘ ์ œํ•œ๋œ ๋ฐ์ดํ„ฐ ์œ ์ถœ ๋ฐฉ์ง€\n\n๊ฐ€์žฅ ์‰ฌ์šด ์œ ์ถœ์€ ์‚ฌ์šฉ์ž๊ฐ€ ๋ณผ ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ธ์ง€ ์•Œ๊ธฐ ์ „์— ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.\n\nVue์—์„œ๋Š” ํŽ˜์ด์ง€๊ฐ€ setup()์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ๋ผ์šฐํ„ฐ ๊ฐ€๋“œ๊ฐ€ ๊ทธ๋ณด๋‹ค ๋Šฆ๊ฒŒ ์‹คํ–‰๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ž์ฃผ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋˜๋”๋ผ๋„ ์‘๋‹ต์ด ๊ณต์œ  ์Šคํ† ์–ด์— ๋‚จ๊ฑฐ๋‚˜ ํ™”๋ฉด์— ์ž ๊น ๋ณด์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\n\n์•ˆ์ „ํ•œ ๊ทœ์น™์€: ๋จผ์ € ๊ถŒํ•œ์„ ํ™•์ธํ•˜๊ณ  ๊ทธ ๋‹ค์Œ ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•˜์„ธ์š”.\n\njs\n// router guard: authorize before entering the route\nrouter.beforeEach(async (to) => {\n await auth.ready() // ensure roles are known\n const required = to.meta.requiredRole\n if (required && !auth.hasRole(required)) {\n return { name: 'forbidden' }\n }\n})\n\n\n๋˜ํ•œ ๋„ค๋น„๊ฒŒ์ด์…˜์ด ๋น ๋ฅด๊ฒŒ ๋ฐ”๋€” ๋•Œ ๋“ค์–ด์˜ค๋Š” ๋Šฆ์€ ์š”์ฒญ์„ ์ฃผ์˜ํ•˜์„ธ์š”. ์š”์ฒญ์„ AbortController๋กœ ์ทจ์†Œํ•˜๊ฑฐ๋‚˜ ์š”์ฒญ ์•„์ด๋””๋ฅผ ํ™•์ธํ•ด ๋Šฆ์€ ์‘๋‹ต์„ ๋ฌด์‹œํ•˜์„ธ์š”.\n\n์บ์‹ฑ๋„ ํ”ํ•œ ํ•จ์ •์ž…๋‹ˆ๋‹ค. "๋งˆ์ง€๋ง‰์œผ๋กœ ๋กœ๋“œํ•œ ๊ณ ๊ฐ ๋ ˆ์ฝ”๋“œ"๋ฅผ ์ „์—ญ์— ์ €์žฅํ•˜๋ฉด ๊ด€๋ฆฌ์ž ์ „์šฉ ์‘๋‹ต์ด ์ดํ›„์— ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž์—๊ฒŒ ํ‘œ์‹œ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์บ์‹œ๋Š” ์‚ฌ์šฉ์ž id์™€ ์—ญํ• ๋กœ ํ‚ค๋ฅผ ๋งŒ๋“ค๊ฑฐ๋‚˜ ๋กœ๊ทธ์•„์›ƒ ์‹œ(๋˜๋Š” ์—ญํ•  ๋ณ€๊ฒฝ ์‹œ) ๋ฏผ๊ฐํ•œ ๋ชจ๋“ˆ์„ ์ •๋ฆฌํ•˜์„ธ์š”.\n\n๋ช‡ ๊ฐ€์ง€ ์Šต๊ด€์œผ๋กœ ๋Œ€๋ถ€๋ถ„ ์œ ์ถœ์„ ์˜ˆ๋ฐฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:\n\n- ๊ถŒํ•œ์ด ํ™•์ธ๋˜๊ธฐ ์ „๊นŒ์ง€ ๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ๋Š” ๊ฐ€์ ธ์˜ค์ง€ ๋งˆ์„ธ์š”.\n- ์บ์‹œ๋ฅผ ์‚ฌ์šฉ์ž์™€ ์—ญํ• ๋กœ ๊ตฌ๋ถ„ํ•˜๊ฑฐ๋‚˜ ํŽ˜์ด์ง€ ๋กœ์ปฌ๋กœ ์œ ์ง€ํ•˜์„ธ์š”.\n- ๋ผ์šฐํŠธ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ์ง„ํ–‰ ์ค‘์ธ ์š”์ฒญ์„ ์ทจ์†Œํ•˜๊ฑฐ๋‚˜ ๋ฌด์‹œํ•˜์„ธ์š”.\n\n## ์นœ์ ˆํ•œ ํด๋ฐฑ: 401, 403, ๊ทธ๋ฆฌ๊ณ  ์ฐพ์„ ์ˆ˜ ์—†์Œ\n\n"๊ฑฐ๋ถ€" ๊ฒฝ๋กœ๋„ ํ—ˆ์šฉ ๊ฒฝ๋กœ๋งŒํผ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ์ข‹์€ ํด๋ฐฑ ํŽ˜์ด์ง€๋Š” ์‚ฌ์šฉ์ž๋ฅผ ๋ฐฉํ–ฅ๊ฐ ์žˆ๊ฒŒ ์•ˆ๋‚ดํ•˜๊ณ  ์ง€์› ์š”์ฒญ์„ ์ค„์—ฌ์ค๋‹ˆ๋‹ค.\n\n### 401: ๋กœ๊ทธ์ธ ํ•„์š”(๋น„์ธ์ฆ)\n\n์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ•˜์ง€ ์•Š์•˜์„ ๋•Œ 401์„ ์‚ฌ์šฉํ•˜์„ธ์š”. ๋ฉ”์‹œ์ง€๋Š” ๋‹จ์ˆœํ•˜๊ฒŒ ์œ ์ง€ํ•˜์„ธ์š”: ๊ณ„์†ํ•˜๋ ค๋ฉด ๋กœ๊ทธ์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์›๋ž˜ ํŽ˜์ด์ง€๋กœ ๋Œ์•„๊ฐ€๋Š” ๊ธฐ๋Šฅ์„ ์ง€์›ํ•˜๋ฉด ๋ฆฌํ„ด ๊ฒฝ๋กœ๋ฅผ ๊ฒ€์ฆํ•ด ์•ฑ ์™ธ๋ถ€๋กœ ๋‚˜๊ฐ€์ง€ ์•Š๋„๋ก ํ•˜์„ธ์š”.\n\n### 403: ์ ‘๊ทผ ๊ฑฐ๋ถ€(์ธ์ฆ๋จ, ๊ทธ๋Ÿฌ๋‚˜ ํ—ˆ์šฉ๋˜์ง€ ์•Š์Œ)\n\n์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ–ˆ์ง€๋งŒ ๊ถŒํ•œ์ด ์—†์„ ๋•Œ 403์„ ์‚ฌ์šฉํ•˜์„ธ์š”. ์ค‘๋ฆฝ์ ์ด๊ณ  ๋ฏผ๊ฐํ•œ ์„ธ๋ถ€์‚ฌํ•ญ์„ ์•”์‹œํ•˜์ง€ ์•Š๋Š” ๋ฌธ๊ตฌ๊ฐ€ ์ข‹์Šต๋‹ˆ๋‹ค.\n\n๊ฒฌ๊ณ ํ•œ 403 ํŽ˜์ด์ง€๋Š” ๋ณดํ†ต ๋ช…ํ™•ํ•œ ์ œ๋ชฉ(โ€œAccess deniedโ€), ํ•œ ๋ฌธ์žฅ์˜ ์„ค๋ช…, ๊ทธ๋ฆฌ๊ณ  ์•ˆ์ „ํ•œ ๋‹ค์Œ ๋‹จ๊ณ„(๋Œ€์‹œ๋ณด๋“œ๋กœ ๋Œ์•„๊ฐ€๊ธฐ, ๊ด€๋ฆฌ์ž์—๊ฒŒ ๋ฌธ์˜, ๊ณ„์ • ์ „ํ™˜)๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.\n\n### 404: ์ฐพ์„ ์ˆ˜ ์—†์Œ\n\n404๋Š” 401/403๊ณผ ๋ณ„๋„๋กœ ์ฒ˜๋ฆฌํ•˜์„ธ์š”. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์‚ฌ์šฉ์ž๋Š” ๋‹จ์ˆœํžˆ ํŽ˜์ด์ง€๊ฐ€ ์—†๋Š”๋ฐ ๊ถŒํ•œ์ด ์—†๋‹ค๊ณ  ์˜คํ•ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\n\n## ์ ‘๊ทผ ์ œ์–ด๋ฅผ ๋ง์น˜๋Š” ์ผ๋ฐ˜์ ์ธ ์‹ค์ˆ˜\n\n๋Œ€๋ถ€๋ถ„์˜ ์ ‘๊ทผ ์ œ์–ด ๋ฒ„๊ทธ๋Š” ๊ฐ„๋‹จํ•œ ๋…ผ๋ฆฌ ์‹ค์ˆ˜๋กœ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค: ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๋ฃจํ”„, ์ž˜๋ชป๋œ ํŽ˜์ด์ง€ ํ”Œ๋ž˜์‹œ, ์‚ฌ์šฉ์ž๊ฐ€ ๋ฉˆ์ถฐ๋ฒ„๋ฆฌ๋Š” ํ˜„์ƒ ๋“ฑ.\n\n์ฃผ๋œ ์›์ธ:\n\n- ์ˆจ๊ธด UI๋ฅผ "๋ณด์•ˆ"์œผ๋กœ ๊ฐ„์ฃผ (ํ•ญ์ƒ ๋ผ์šฐํ„ฐ์™€ API์—์„œ ์—ญํ• ์„ ๊ฐ•์ œ ์ ์šฉ)\n- ๋กœ๊ทธ์•„์›ƒ/๋กœ๊ทธ์ธ ํ›„ ์˜ค๋ž˜๋œ ์ƒํƒœ์—์„œ ์—ญํ• ์„ ์ฝ์Œ\n- ๊ถŒํ•œ ์—†๋Š” ์‚ฌ์šฉ์ž๋ฅผ ๋‹ค๋ฅธ ๋ณดํ˜ธ๋œ ๋ผ์šฐํŠธ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ(์ฆ‰์‹œ ๋ฃจํ”„ ๋ฐœ์ƒ)\n- ์ƒˆ๋กœ๊ณ ์นจ ์‹œ "์ธ์ฆ ๋กœ๋”ฉ ์ค‘" ์ˆœ๊ฐ„์„ ๋ฌด์‹œํ•จ\n- 401๊ณผ 403์„ ํ˜ผ๋™ํ•ด ์‚ฌ์šฉ์ž ํ˜ผ๋ž€์„ ์ดˆ๋ž˜ํ•จ\n\nํ˜„์‹ค์ ์ธ ์˜ˆ: ์ง€์› ๋‹ด๋‹น์ž๊ฐ€ ๋กœ๊ทธ์•„์›ƒํ•˜๊ณ  ๊ฐ™์€ ์ปดํ“จํ„ฐ์—์„œ ๊ด€๋ฆฌ์ž๊ฐ€ ๋กœ๊ทธ์ธํ•  ๋•Œ, ๊ฐ€๋“œ๊ฐ€ ์ƒˆ ์„ธ์…˜์ด ํ™•์ธ๋˜๊ธฐ ์ „์— ์บ์‹œ๋œ ์—ญํ• ์„ ์ฝ์œผ๋ฉด ๊ด€๋ฆฌ์ž๋ฅผ ์ž˜๋ชป ์ฐจ๋‹จํ•˜๊ฑฐ๋‚˜, ๋” ์œ„ํ—˜ํ•˜๊ฒŒ๋Š” ์ž ๊น ํ—ˆ์šฉํ•ด ๋ฒ„๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\n\n## ๋ฐฐํฌ ์ „ ๋น ๋ฅธ ์ฒดํฌ๋ฆฌ์ŠคํŠธ\n\n๋А๋ฆฐ ๋„คํŠธ์›Œํฌ, ๋งŒ๋ฃŒ๋œ ์„ธ์…˜, ๋ถ๋งˆํฌ๋œ URL ๊ฐ™์€ ์ ‘๊ทผ ์ œ์–ด๊ฐ€ ๋ณดํ†ต ๊นจ์ง€๋Š” ์ˆœ๊ฐ„์— ์ง‘์ค‘ํ•ด ์งง๊ฒŒ ์ ๊ฒ€ํ•˜์„ธ์š”.\n\n- ๋ชจ๋“  ๋ณดํ˜ธ๋œ ๋ผ์šฐํŠธ์— ๋ช…์‹œ์  meta ์š”๊ตฌ์‚ฌํ•ญ ์กด์žฌ\n- ๊ฐ€๋“œ๋Š” ์ธ์ฆ ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ์ฒ˜๋ฆฌํ•ด ๋ณดํ˜ธ๋œ UI๊ฐ€ ๊นœ๋นก์ด์ง€ ์•Š์Œ\n- ๊ถŒํ•œ ์—†๋Š” ์‚ฌ์šฉ์ž๋Š” ๋ช…ํ™•ํ•œ 403 ํŽ˜์ด์ง€๋กœ ์ด๋™(ํ˜ผ๋ž€์Šค๋Ÿฌ์šด ํ™ˆ์œผ๋กœ์˜ ํŠ•๊น€ ์•„๋‹˜)\n- ๋ชจ๋“  "๋Œ์•„๊ฐ€๊ธฐ" ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋Š” ๊ฒ€์ฆ๋˜์–ด ๋ฃจํ”„๋ฅผ ๋งŒ๋“ค์ง€ ์•Š์Œ\n- ๋ฏผ๊ฐํ•œ API ํ˜ธ์ถœ์€ ๊ถŒํ•œ ํ™•์ธ ํ›„์—๋งŒ ์‹คํ–‰๋จ\n\n๊ทธ๋Ÿฐ ๋‹ค์Œ ํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๋๊นŒ์ง€ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”: ๋กœ๊ทธ์•„์›ƒ ์ƒํƒœ์—์„œ ๋ณดํ˜ธ๋œ URL์„ ์ƒˆ ํƒญ์œผ๋กœ ์—ด๊ณ , ๊ธฐ๋ณธ ์‚ฌ์šฉ์ž๋กœ ๋กœ๊ทธ์ธํ•œ ๋’ค ์˜๋„๋œ ํŽ˜์ด์ง€๋กœ ๊ฐ€์•ผ ํ•˜๋Š”์ง€(ํ—ˆ์šฉ๋œ๋‹ค๋ฉด) ๋˜๋Š” ๊น”๋”ํ•œ 403๊ณผ ๋‹ค์Œ ๋‹จ๊ณ„๋ฅผ ํ™•์ธํ•˜์„ธ์š”.\n\n## ์˜ˆ์‹œ: ์ž‘์€ ์›น ์•ฑ์—์„œ ์ง€์›๊ณผ ๊ด€๋ฆฌ์ž ์ ‘๊ทผ\n\nํ—ฌํ”„๋ฐ์Šคํฌ ์•ฑ์„ ๊ฐ€์ •ํ•ด ๋ณด์„ธ์š”. ๋‘ ๊ฐ€์ง€ ์—ญํ• ์ด ์žˆ์Šต๋‹ˆ๋‹ค: support์™€ admin. Support๋Š” ํ‹ฐ์ผ“์„ ์ฝ๊ณ  ์‘๋‹ตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Admin์€ ๊ทธ ์™ธ์—๋„ ๋นŒ๋ง๊ณผ ํšŒ์‚ฌ ์„ค์ •์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\n\n- /tickets/:id๋Š” support์™€ admin์— ํ—ˆ์šฉ\n- /settings/billing์€ admin๋งŒ ํ—ˆ์šฉ\n\nํ”ํ•œ ์ƒํ™ฉ: support ๋‹ด๋‹น์ž๊ฐ€ ์˜ค๋ž˜๋œ ์ฆ๊ฒจ์ฐพ๊ธฐ์—์„œ /settings/billing๋กœ ๋”ฅ ๋งํฌ๋ฅผ ์—ฝ๋‹ˆ๋‹ค. ๊ฐ€๋“œ๋Š” ํŽ˜์ด์ง€๊ฐ€ ๋กœ๋“œ๋˜๊ธฐ ์ „์— route meta๋ฅผ ํ™•์ธํ•ด ๋„ค๋น„๊ฒŒ์ด์…˜์„ ์ฐจ๋‹จํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๋Š” ๋กœ๊ทธ์ธ์€ ํ–ˆ์ง€๋งŒ ์—ญํ• ์ด ๋ถ€์กฑํ•˜๋ฏ€๋กœ ์•ˆ์ „ํ•œ ํด๋ฐฑ(403)์œผ๋กœ ๊ฐ€์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n\n๋‘ ๊ฐ€์ง€ ๋ฉ”์‹œ์ง€๊ฐ€ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค:\n\n- ๋กœ๊ทธ์ธ ํ•„์š”(401): โ€œ๊ณ„์†ํ•˜๋ ค๋ฉด ๋กœ๊ทธ์ธํ•˜์„ธ์š”.โ€\n- ์ ‘๊ทผ ๊ฑฐ๋ถ€(403): โ€œ์ฒญ๊ตฌ ์„ค์ •์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.โ€\n\n์ ˆ๋Œ€ ๋ฐœ์ƒํ•˜๋ฉด ์•ˆ ๋˜๋Š” ๊ฒƒ: billing ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ๋˜๊ฑฐ๋‚˜ billing ๋ฐ์ดํ„ฐ๋ฅผ ์ž ๊น์ด๋ผ๋„ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ.\n\n์„ธ์…˜ ์ค‘ ์—ญํ•  ๋ณ€๊ฒฝ๋„ ์—ฃ์ง€ ์ผ€์ด์Šค์ž…๋‹ˆ๋‹ค. ๋ˆ„๊ตฐ๊ฐ€ ์Šน๊ฒฉ๋˜๊ฑฐ๋‚˜ ๊ฐ•๋“ฑ๋˜๋ฉด ๋ฉ”๋‰ด๋งŒ ๋ฏฟ์ง€ ๋งˆ์„ธ์š”. ๋„ค๋น„๊ฒŒ์ด์…˜ ์‹œ ์—ญํ• ์„ ์žฌํ™•์ธํ•˜๊ณ  ํ™œ์„ฑ ํŽ˜์ด์ง€๊ฐ€ ๋” ์ด์ƒ ํ—ˆ์šฉ๋˜์ง€ ์•Š์œผ๋ฉด ์ƒˆ๋กœ๊ณ ์นจํ•˜๊ฑฐ๋‚˜ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•˜์„ธ์š”.\n\n## ๋‹ค์Œ ๋‹จ๊ณ„: ์ ‘๊ทผ ๊ทœ์น™์„ ์œ ์ง€ ๊ฐ€๋Šฅํ•˜๊ฒŒ ์œ ์ง€\n\n๊ฐ€๋“œ๊ฐ€ ๋™์ž‘ํ•˜๋ฉด ๋” ํฐ ์œ„ํ—˜์€ ๊ทœ์น™์˜ ๋ถˆ์ผ์น˜์ž…๋‹ˆ๋‹ค: ์ƒˆ ๋ผ์šฐํŠธ๊ฐ€ meta ์—†์ด ๋ฐฐํฌ๋˜๊ฑฐ๋‚˜ ์—ญํ• ์ด ์ด๋ฆ„์ด ๋ฐ”๋€Œ๋Š” ๋“ฑ ๊ทœ์น™์ด ์ผ๊ด€์„ฑ์„ ์žƒ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\n\n๋ผ์šฐํŠธ๋ฅผ ์ถ”๊ฐ€ํ•  ๋•Œ๋งˆ๋‹ค ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ์ž‘์€ ํ…Œ์ŠคํŠธ ํ”Œ๋žœ์œผ๋กœ ๊ทœ์น™์„ ๊ด€๋ฆฌํ•˜์„ธ์š”:\n\n- ๊ฒŒ์ŠคํŠธ๋กœ์„œ ๋ณดํ˜ธ๋œ ๋ผ์šฐํŠธ๋ฅผ ์—ด์–ด ๋ถ€๋ถ„ ์ฝ˜ํ…์ธ ๊ฐ€ ๋ณด์ด์ง€ ์•Š๊ณ  ๋กœ๊ทธ์ธ์œผ๋กœ ๊ฐ€๋Š”์ง€ ํ™•์ธ\n- ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž๋กœ ์ ‘๊ทผ ๋ถˆ๊ฐ€ํ•œ ํŽ˜์ด์ง€๋ฅผ ์—ด์–ด ๋ช…ํ™•ํ•œ 403์ด ๋œจ๋Š”์ง€ ํ™•์ธ\n- ๊ด€๋ฆฌ์ž ๊ถŒํ•œ์œผ๋กœ ์ฃผ์†Œ์ฐฝ์—์„œ ๋”ฅ ๋งํฌ๋ฅผ ์‹œ๋„\n- ๊ฐ ์—ญํ• ๋ณ„๋กœ ๋ณดํ˜ธ๋œ ๋ผ์šฐํŠธ์—์„œ ์ƒˆ๋กœ๊ณ ์นจํ•ด ๊ฒฐ๊ณผ๊ฐ€ ์•ˆ์ •์ ์ธ์ง€ ํ™•์ธ\n\n์ถ”๊ฐ€ ์•ˆ์ „๋ง์œผ๋กœ ๊ฐœ๋ฐœ ์ „์šฉ ๋ทฐ๋‚˜ ์ฝ˜์†” ์ถœ๋ ฅ์„ ์ถ”๊ฐ€ํ•ด ๋ผ์šฐํŠธ์™€ ๊ทธ meta ์š”๊ตฌ์‚ฌํ•ญ์„ ๋‚˜์—ดํ•˜๋ฉด ๋ˆ„๋ฝ๋œ ๊ทœ์น™์ด ์ฆ‰์‹œ ๋“œ๋Ÿฌ๋‚ฉ๋‹ˆ๋‹ค.\n\nAppMaster (appmaster.io)๋กœ ๋‚ด๋ถ€ ๋„๊ตฌ๋‚˜ ํฌํ„ธ์„ ๋งŒ๋“ ๋‹ค๋ฉด ๋™์ผํ•œ ์ ‘๊ทผ๋ฒ•์„ ์ ์šฉํ•˜์„ธ์š”: Vue3 UI์—์„œ๋Š” ๋„ค๋น„๊ฒŒ์ด์…˜์— ๊ฐ€๋“œ๋ฅผ ์ง‘์ค‘์‹œํ‚ค๊ณ , ๊ถŒํ•œ๊ณผ ๋ฐ์ดํ„ฐ๋Š” ๋ฐฑ์—”๋“œ ๋กœ์ง์—์„œ ๊ฐ•์ œํ•˜์„ธ์š”.\n\nํ•œ ๊ฐ€์ง€ ๊ฐœ์„ ์ ์„ ๊ณจ๋ผ ๋๊นŒ์ง€ ๊ตฌํ˜„ํ•˜์„ธ์š”: ๋ฐ์ดํ„ฐ ํŽ˜์นญ ๊ฒŒ์ดํŒ… ๊ฐ•ํ™”, 403 ํŽ˜์ด์ง€ ๊ฐœ์„ , ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์ฒ˜๋ฆฌ ์ž ๊ธˆ ๋“ฑ. ์ž‘์€ ์ˆ˜์ •์ด ์‹ค์ œ ์ ‘๊ทผ ๋ฒ„๊ทธ ๋Œ€๋ถ€๋ถ„์„ ๋ง‰์Šต๋‹ˆ๋‹ค.

์ž์ฃผ ๋ฌป๋Š” ์งˆ๋ฌธ

๋ผ์šฐํŠธ ๊ฐ€๋“œ๋Š” ์‹ค์ œ๋กœ ๋ณด์•ˆ์ธ๊ฐ€์š”, ์•„๋‹ˆ๋ฉด ๋‹จ์ง€ UX์ธ๊ฐ€์š”?

๋ผ์šฐํŠธ ๊ฐ€๋“œ๋Š” ๋„ค๋น„๊ฒŒ์ด์…˜์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. ํŽ˜์ด์ง€ ์ง„์ž…์„ ๋ง‰๊ณ  ์ ์ ˆํžˆ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•˜๊ฑฐ๋‚˜ ๊น”๋”ํ•œ 401/403 ์ƒํƒœ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๋ฐ ๋„์›€์„ ์ฃผ์ง€๋งŒ, API๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์„ ๋ง‰์ง€๋Š” ๋ชปํ•ฉ๋‹ˆ๋‹ค. ์ œํ•œ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ธŒ๋ผ์šฐ์ €๋กœ ๋ฐ˜ํ™˜๋˜์ง€ ์•Š๋„๋ก ์„œ๋ฒ„์—์„œ๋„ ๋™์ผํ•œ ๊ถŒํ•œ ๊ฒ€์‚ฌ๋ฅผ ํ•ญ์ƒ ์ ์šฉํ•˜์„ธ์š”.

๋ฉ”๋‰ด ํ•ญ๋ชฉ ์ˆจ๊ธฐ๊ธฐ๋งŒ์œผ๋กœ ์—ญํ•  ๊ธฐ๋ฐ˜ ์ ‘๊ทผ์ด ์ถฉ๋ถ„ํ•œ๊ฐ€์š”?

UI๋ฅผ ์ˆจ๊ธฐ๋Š” ๊ฒƒ์€ ์‚ฌ์šฉ์ž๊ฐ€ ๋ณด๋Š” ๊ฒƒ๋งŒ ๋ฐ”๊ฟ€ ๋ฟ, ์š”์ฒญํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์„ ๋ง‰์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๋Š” URL์„ ์ง์ ‘ ์ž…๋ ฅํ•˜๊ฑฐ๋‚˜ ์ฆ๊ฒจ์ฐพ๊ธฐ, ๋”ฅ ๋งํฌ๋กœ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŽ˜์ด์ง€ ์ฐจ๋‹จ์€ ๋ผ์šฐํ„ฐ์—์„œ ํ•˜๊ณ , ๋ฐ์ดํ„ฐ ์ฐจ๋‹จ์€ ์„œ๋ฒ„์—์„œ ํ•˜์„ธ์š”.

์‹œ์ž‘ํ•˜๊ธฐ์— ์ข‹์€ ๊ฐ„๋‹จํ•œ ์—ญํ•  ๋ฐ ๊ถŒํ•œ ๋ชจ๋ธ์€ ๋ฌด์—‡์ธ๊ฐ€์š”?

์‚ฌ๋žŒ๋“ค์ด ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š” ์ž‘์€ ์ง‘ํ•ฉ์œผ๋กœ ์‹œ์ž‘ํ•˜๊ณ , ์‹ค์ œ๋กœ ๋ถˆํŽธํ•จ์„ ๋А๋‚„ ๋•Œ ์„ธ๋ถ€ ๊ถŒํ•œ์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”. ์ผ๋ฐ˜์ ์ธ ๊ธฐ๋ณธ ๊ตฌ์„ฑ์€ admin, support, viewer์ด๋ฉฐ, ํŠน์ • ํ–‰๋™์—๋Š” tickets:read๋‚˜ audit:read ๊ฐ™์€ ๊ถŒํ•œ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. โ€˜๋ˆ„๊ตฌ์ธ์ง€โ€™(role)์™€ โ€˜๋ฌด์—‡์„ ํ•  ์ˆ˜ ์žˆ๋Š”์ง€โ€™(permission)๋ฅผ ๋ถ„๋ฆฌํ•˜์„ธ์š”.

Vue Router์˜ meta๋Š” ์ ‘๊ทผ ์ œ์–ด์— ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋‚˜์š”?

meta์— ์ ‘๊ทผ ๊ทœ์น™์„ ๊ธฐ๋กํ•˜์„ธ์š”. ๋ผ์šฐํŠธ ๋ ˆ์ฝ”๋“œ์— requiresAuth, roles, permissions ๊ฐ™์€ ๊ฐ’์„ ๋‘๋ฉด ์ „์—ญ ๊ฐ€๋“œ๊ฐ€ ์˜ˆ์ธก ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. ์ค‘์ฒฉ ๋ผ์šฐํŠธ์˜ ๊ฒฝ์šฐ ๋ถ€๋ชจ ์š”๊ตฌ์‚ฌํ•ญ์ด ๋ˆ„๋ฝ๋˜์ง€ ์•Š๋„๋ก ๋ชจ๋“  ๋งค์น˜๋œ ๋ ˆ์ฝ”๋“œ๋ฅผ ๊ฒ€์‚ฌํ•˜์„ธ์š”.

์ค‘์ฒฉ ๋ผ์šฐํŠธ๋ฅผ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•ด์•ผ ์ž์‹์ด ๋ถ€๋ชจ ์ œ์•ฝ์„ ์ƒ์†ํ•˜๋‚˜์š”?

๋ผ์šฐํŠธ์˜ to.matched๋ฅผ ์ฝ์–ด ๋ชจ๋“  ๋งค์น˜๋œ ๋ ˆ์ฝ”๋“œ์˜ ์š”๊ตฌ์‚ฌํ•ญ์„ ํ•ฉ์น˜์„ธ์š”. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ž์‹ ๋ผ์šฐํŠธ๊ฐ€ ๋ถ€๋ชจ์˜ requiresAuth๋‚˜ roles๋ฅผ ์šฐํšŒํ•˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. ๋ณดํ†ต์€ ๋ถ€๋ชจ ์š”๊ตฌ์‚ฌํ•ญ์ด ์ž์‹์—๊ฒŒ ์ ์šฉ๋œ๋‹ค๊ณ  ์ •ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

์ƒˆ๋กœ๊ณ ์นจ ์‹œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๋ฃจํ”„์™€ ๋ณดํ˜ธ๋œ ํŽ˜์ด์ง€์˜ ํ”Œ๋ž˜์‹œ๋ฅผ ์–ด๋–ป๊ฒŒ ๋ง‰๋‚˜์š”?

๊ฐ€๋“œ๊ฐ€ ์•ฑ์ด ์‚ฌ์šฉ์ž๋ฅผ ๋ชจ๋ฅผ ๋•Œ ์‹คํ–‰๋˜๋ฉด ํ”Œ๋ž˜์‹œ๋‚˜ ๋ฌดํ•œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๊ฐ€ ์ƒ๊น๋‹ˆ๋‹ค. ์ธ์ฆ ์ƒํƒœ๋ฅผ unknown, logged out, logged in์˜ ์„ธ ๊ฐ€์ง€๋กœ ๋‹ค๋ฃจ๊ณ , unknown์ผ ๋•Œ๋Š” ์—ญํ• ์„ ์ ˆ๋Œ€ ํŒ๋‹จํ•˜์ง€ ๋งˆ์„ธ์š”. ๊ฐ€๋“œ๋Š” ์ดˆ๊ธฐํ™”(์˜ˆ: who am I)๊ฐ€ ๋๋‚  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ค์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ „์—ญ beforeEach ๊ฐ€๋“œ์™€ beforeEnter๋Š” ์–ธ์ œ ์‚ฌ์šฉํ•˜๋‚˜์š”?

์ผ๊ด€๋œ ๊ทœ์น™(์˜ˆ: โ€˜๋กœ๊ทธ์ธ ํ•„์š”โ€™, โ€˜๊ถŒํ•œ ํ•„์š”โ€™)์€ ์ „์—ญ beforeEach์—์„œ ์ฒ˜๋ฆฌํ•˜์„ธ์š”. beforeEnter๋Š” to.params.id์ฒ˜๋Ÿผ ๊ฒฝ๋กœ๋ณ„๋กœ ์ •๋ง ํŠน๋ณ„ํ•œ ๊ฒ€์‚ฌ(์˜ˆ: ํ‹ฐ์ผ“ ์†Œ์œ ์ž ํ™•์ธ)๊ฐ€ ํ•„์š”ํ•  ๋•Œ๋งŒ ์“ฐ์„ธ์š”. ๋‘ ๊ฒฝ์šฐ ๋ชจ๋‘ ๋™์ผํ•œ ์ธ์ฆ ์†Œ์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ํ•˜์„ธ์š”.

๋กœ๊ทธ์ธ ํ›„ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ•˜๋‚˜์š”?

returnTo๋Š” ์‹ ๋ขฐํ•  ์ˆ˜ ์—†๋Š” ์ž…๋ ฅ์œผ๋กœ ์ทจ๊ธ‰ํ•˜์„ธ์š”. ๋‚ด๋ถ€ ๊ฒฝ๋กœ๋งŒ ํ—ˆ์šฉํ•˜๊ณ (์˜ˆ: /๋กœ ์‹œ์ž‘ํ•˜๊ณ  ํ—ˆ์šฉ๋œ ์ ‘๋‘์‚ฌ์™€ ๋งค์น˜๋˜๋Š” ๊ฐ’), ๋™์ผํ•œ ์ฐจ๋‹จ๋œ ๊ฒฝ๋กœ๋กœ ๋‹ค์‹œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•˜์ง€ ์•Š๋„๋ก ๋ฃจํ”„ ์ฒดํฌ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”. ๋กœ๊ทธ์•„์›ƒ๋œ ์‚ฌ์šฉ์ž๋Š” ๋กœ๊ทธ์ธ์œผ๋กœ, ๋กœ๊ทธ์ธ์€ ํ–ˆ์ง€๋งŒ ๊ถŒํ•œ์ด ์—†๋Š” ์‚ฌ์šฉ์ž๋Š” ์ „์šฉ 403 ํŽ˜์ด์ง€๋กœ ๋ณด๋‚ด์„ธ์š”.

๋„ค๋น„๊ฒŒ์ด์…˜ ์ค‘ ๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ ์œ ์ถœ์„ ์–ด๋–ป๊ฒŒ ๋ฐฉ์ง€ํ•˜๋‚˜์š”?

์ธ์ฆ์ด ํ™•์ธ๋˜๊ธฐ ์ „์— ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉด ์œ ์ถœ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŽ˜์ด์ง€๊ฐ€ setup()์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•˜๊ณ  ๊ฐ€๋“œ๊ฐ€ ๊ทธ ์งํ›„ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•˜๋ฉด ์‘๋‹ต์ด ์Šคํ† ์–ด์— ๋‚จ๊ฑฐ๋‚˜ ์ž ๊น ํ™”๋ฉด์— ๋ณด์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฏผ๊ฐํ•œ ์š”์ฒญ์€ ๊ถŒํ•œ ํ™•์ธ ๋’ค์— ์ˆ˜ํ–‰ํ•˜๊ณ , ๋„ค๋น„๊ฒŒ์ด์…˜์ด ๋ฐ”๋€” ๋•Œ๋Š” ์š”์ฒญ์„ ์ทจ์†Œํ•˜๊ฑฐ๋‚˜ ๋Šฆ์€ ์‘๋‹ต์„ ๋ฌด์‹œํ•˜์„ธ์š”.

Vue ์•ฑ์—์„œ 401, 403, 404๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€?

401์€ ๋กœ๊ทธ์ธ ํ•„์š”(๋น„์ธ์ฆ), 403์€ ์ธ์ฆ์€ ๋์ง€๋งŒ ๊ถŒํ•œ ์—†์Œ(์ ‘๊ทผ ๊ฑฐ๋ถ€), 404๋Š” ์กด์žฌํ•˜์ง€ ์•Š๋Š” ํŽ˜์ด์ง€๋กœ ์ฒ˜๋ฆฌํ•˜์„ธ์š”. 404์™€ 403์„ ์„ž์ง€ ์•Š์œผ๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ๋‹จ์ˆœํžˆ ํŽ˜์ด์ง€๊ฐ€ ์—†๋Š”๋ฐ ๊ถŒํ•œ์ด ์—†๋‹ค๊ณ  ์˜คํ•ดํ•˜๋Š” ์ผ์ด ์ค„์–ด๋“ญ๋‹ˆ๋‹ค.

์‰ฌ์šด ์‹œ์ž‘
๋ฉ‹์ง„๋งŒ๋“ค๊ธฐ

๋ฌด๋ฃŒ ์š”๊ธˆ์ œ๋กœ AppMaster๋ฅผ ์‚ฌ์šฉํ•ด ๋ณด์„ธ์š”.
์ค€๋น„๊ฐ€ ๋˜๋ฉด ์ ์ ˆํ•œ ๊ตฌ๋…์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‹œ์ž‘ํ•˜๋‹ค
Vue 3 ์—ญํ•  ๊ธฐ๋ฐ˜ ์ ‘๊ทผ์„ ์œ„ํ•œ ๋ผ์šฐํŒ… ๊ฐ€๋“œ: ์‹ค์ „ ํŒจํ„ด | AppMaster