사이드바 동적 생성은 전달된 라우팅 정보를 기반으로 결정됩니다. 소스코드 내부 구조가 복잡해 별도로 정리할 예정입니다.
권한 처리 및 레이아웃 구성
프로세스 개요
페이지 초기화 시 페이지 로딩, 메뉴 생성, 사용자 권한 확인 등 여러 단계가 포함됩니다. 다음 사항을 고려해야 합니다:
- 로그인 성공 시 NProgress를 통해 토큰을 획득하고 유효성 검증 수행
- 사용자 역할 정보를 API에서 받아 라우팅 테이블에 동적 메뉴 적용
- 권한 확보 후 홈 레이아웃으로 이동하며 사이드바는 허용된 메뉴 기준으로 생성
라우터 재구성
라우터를 정적 경로와 동적 권한 경로로 분리합니다. 기본 페이지는 대시보드로 설정합니다. meta 속성을 모두 구성해야 하며, 동적 로딩 시 URL 매칭 오류 문제가 발생했습니다.
// 404 페이지는 동적 라우트 마지막에 배치해야 함
{ path: '*', redirect: '/404', hidden: true }
vue-router 문서에 따르면, 通配符 라우트는 항상 마지막에 배치해야 하며 History 모드일 경우 서버 설정이 필수적입니다.
JS-COOKIE 도입
토큰 저장을 위해 JS-COOKIE 라이브러리를 사용합니다. 사용자 인증 후 토큰을 쿠키에 저장하고, utils 폴더에 auth.js를 추가하여 관리합니다.
import Cookies from "js-cookie";
const authTokenKey = "Admin-Token";
export function getAuthToken() {
return Cookies.get(authTokenKey);
}
export function setAuthToken(token) {
return Cookies.set(authTokenKey, token);
}
export function removeAuthToken() {
return Cookies.remove(authTokenKey);
}
쿠키 동기화 문제(다른 포트)는 추후 해결해야 할 점입니다.
사용자 인증 업데이트
store/modules/user.js 파일을 수정하여 토큰과 역할 정보를 관리합니다.
import router from "@/router";
import { setAuthToken, getAuthToken } from "@/utils/auth";
import { getInfo, login } from "@/api/user";
const state = {
userDetails: {
name: "",
token: getAuthToken(),
password: "",
roles: []
}
};
const actions = {
async fetchUserInfo({ commit, state }) {
const response = await getInfo(state.userDetails.token);
const { data } = response;
if (!data) throw new Error("인증 실패");
const { roles, name, token } = data.userInfo;
if (!roles || roles.length <= 0) throw new Error("역할 정보 누락");
commit("SET_ROLES", roles);
commit("SET_NAME", name);
commit("SET_TOKEN", token);
return data;
},
async handleLogin({ commit }, payload) {
const { username, password } = payload;
const response = await login(username.trim(), password);
if (response.data.userInfo.token !== "error") {
commit("SET_ROLES", response.data.userInfo.roles);
commit("SET_NAME", response.data.userInfo.name);
commit("SET_TOKEN", response.data.userInfo.token);
setAuthToken(response.data.userInfo.token);
router.push("/");
} else {
router.push("/404");
}
}
};
권한 검증 로직
nprogress를 설치하고, permission.js 파일을 통해 토큰을 기반으로 권한 검사를 수행합니다.
import router from "./router";
import store from "./store";
import NProgress from "nprogress";
import "nprogress/nprogress.css";
import { getAuthToken } from "@/utils/auth";
NProgress.configure({ showSpinner: false });
const allowedPaths = ["/user/login", "/auth-redirect"];
router.beforeEach(async (to, from, next) => {
NProgress.start();
const isAuthenticated = getAuthToken();
if (isAuthenticated) {
if (to.path === "/user/login") {
next({ path: "/" });
NProgress.done();
return;
}
const hasRoles = store.getters.roles && store.getters.roles.length > 0;
if (hasRoles) {
next();
} else {
try {
const userInfo = await store.dispatch("user/fetchUserInfo");
const { roles } = userInfo;
const accessRoutes = await store.dispatch(
"permission/generateRoutes",
roles
);
router.addRoutes(accessRoutes);
next({ ...to, replace: true });
} catch (error) {
await store.dispatch("user/resetToken");
next(`/user/login?redirect=${to.path}`);
NProgress.done();
}
}
} else {
if (allowedPaths.includes(to.path)) {
next();
} else {
next(`user/login?redirect=${to.path}`);
NProgress.done();
}
}
});
router.afterEach(() => {
NProgress.done();
});
레이아웃 구성
레이아웃 파일(src/layouts/index.vue)은 다음과 같이 구성됩니다:
<template>
<div :class="layoutClasses" class="app-wrapper">
<sidebar />
<div :class="{ hasTagsView: showTagsView }" class="main-container">
<div :class="{ 'fixed-header': fixedHeader }">
<navbar />
<tags-view v-if="showTagsView" />
</div>
<app-main />
<right-panel />
</div>
</div>
</template>
앱 메인 구성
AppMain.vue 파일은 다음과 같은 구조를 가집니다:
<template>
<section class="app-main">
<transition name="fade-transform" mode="out-in">
<router-view :key="viewKey" />
</transition>
</section>
</template>
<script>
export default {
computed: {
viewKey() {
return this.$route.path;
}
}
};
</script>