라우트 가드
12.2.1. 전역 가드
- 전역 가드는
router.beforeEach를 사용하여 등록합니다. - Vue Router 4에서는
next()콜백 대신 반환값을 사용합니다.next()는 사용하지 않습니다. - 네비게이션을 허용하려면
true또는undefined를 반환합니다. - 네비게이션을 차단하려면
false또는 리다이렉트 경로를 반환합니다.
typescript
// src/router/index.ts
router.beforeEach((to, from) => {
// 인증이 필요하지 않은 페이지는 통과
if (!to.meta.requiresAuth) {
return true
}
const authStore = useAuthStore()
if (!authStore.isAuthenticated) {
return { name: 'Login', query: { redirect: to.fullPath } }
}
return true
})router.afterEach는 페이지 제목 설정, 분석 이벤트 전송 등에 사용합니다.
typescript
router.afterEach((to) => {
document.title = `${to.meta.title} | TQS`
})12.2.2. 라우트별 가드
- 특정 라우트에만 적용되는 가드는
beforeEnter속성을 사용합니다. - 권한 기반 접근 제어는
meta.roles와 함께 라우트별 가드로 구현합니다. - 가드 로직이 복잡할 경우 별도 함수로 분리합니다.
typescript
// src/router/guards/roleGuard.ts
import type { NavigationGuardWithThis } from 'vue-router'
import { useAuthStore } from '@/stores/useAuthStore'
export function requireRoles(
allowedRoles: string[]
): NavigationGuardWithThis<undefined> {
return () => {
const authStore = useAuthStore()
const userRole = authStore.user?.role
if (!userRole || !allowedRoles.includes(userRole)) {
return { name: 'Forbidden' }
}
return true
}
}typescript
// src/router/routes.ts
import { requireRoles } from './guards/roleGuard'
{
path: '/admin/dashboard',
name: 'AdminDashboard',
component: () => import('@/views/admin/DashboardView.vue'),
beforeEnter: requireRoles(['admin', 'super-admin']),
meta: { title: '관리자 대시보드', requiresAuth: true, roles: ['admin'] },
}12.2.3. 컴포넌트 내 가드
- 컴포넌트 내 가드는 Composition API의
onBeforeRouteLeave와onBeforeRouteUpdate를 사용합니다. - Options API의
beforeRouteEnter,beforeRouteLeave는 사용하지 않습니다. - 미저장 데이터가 있을 때 페이지 이탈을 경고하는 패턴에
onBeforeRouteLeave를 사용합니다.
vue
<script setup lang="ts">
import { ref } from 'vue'
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
const hasUnsavedChanges = ref(false)
onBeforeRouteLeave(() => {
if (hasUnsavedChanges.value) {
const answer = window.confirm('저장하지 않은 변경사항이 있습니다. 이동하시겠습니까?')
if (!answer) {
return false
}
}
})
onBeforeRouteUpdate(async (to) => {
// 같은 컴포넌트에서 파라미터만 변경될 때 데이터를 다시 로드
await fetchData(to.params.id as string)
})
</script>12.2.4. 인증 가드 구현
- 인증 가드는 다음 순서로 동작해야 합니다.
| 단계 | 동작 | 실패 시 |
|---|---|---|
| 1 | 토큰 존재 여부 확인 | 로그인 페이지로 리다이렉트 |
| 2 | 토큰 유효성 검증 | 토큰 갱신 시도 |
| 3 | 사용자 정보 로드 | 로그인 페이지로 리다이렉트 |
| 4 | 권한 검증 | 403 페이지로 리다이렉트 |
typescript
// src/router/guards/authGuard.ts
import { useAuthStore } from '@/stores/useAuthStore'
export async function authGuard(to: RouteLocationNormalized) {
if (!to.meta.requiresAuth) {
return true
}
const authStore = useAuthStore()
// 1단계: 토큰 확인
if (!authStore.token) {
return { name: 'Login', query: { redirect: to.fullPath } }
}
// 2단계: 토큰 유효성 검증 및 갱신
if (authStore.isTokenExpired) {
const refreshed = await authStore.refreshToken()
if (!refreshed) {
authStore.clearAuth()
return { name: 'Login', query: { redirect: to.fullPath } }
}
}
// 3단계: 사용자 정보 로드
if (!authStore.user) {
try {
await authStore.fetchUser()
} catch {
authStore.clearAuth()
return { name: 'Login', query: { redirect: to.fullPath } }
}
}
// 4단계: 권한 검증
const requiredRoles = to.meta.roles
if (requiredRoles && !requiredRoles.includes(authStore.user.role)) {
return { name: 'Forbidden' }
}
return true
}typescript
// src/router/index.ts
import { authGuard } from './guards/authGuard'
router.beforeEach(authGuard)- 인증 가드는 전역
beforeEach에 단일 진입점으로 등록합니다. - 로그인 페이지 리다이렉트 시 원래 경로를
query.redirect에 보존합니다. - 토큰 갱신 실패 시 저장된 인증 정보를 모두 초기화합니다.