vue-element-admin 학습 노트 - 권한 처리 및 커스텀 레이아웃 구현

사이드바 동적 생성은 전달된 라우팅 정보를 기반으로 결정됩니다. 소스코드 내부 구조가 복잡해 별도로 정리할 예정입니다.

권한 처리 및 레이아웃 구성

프로세스 개요

페이지 초기화 시 페이지 로딩, 메뉴 생성, 사용자 권한 확인 등 여러 단계가 포함됩니다. 다음 사항을 고려해야 합니다:

  • 로그인 성공 시 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>

태그: vue-element-admin 권한관리 레이아웃구성 vue-router vuex

6월 12일 17:12에 게시됨