๊ฐœ๋ฐœ๋…ธํŠธ/Note

[TIL] Vue Router Guard (2022-08-01 ๋‚ด์šฉ ์ด์ „)

Dahoon06 2022. 9. 13. 11:14
728x90
๋ฐ˜์‘ํ˜•
๐Ÿ’ก ๋ฌธ์ œ์ 
์œ ๋‹ˆ์„œ๋ฒ ์ด ํ…Œ์ŠคํŠธ ์ค‘์— URL์— ํ”„๋กœ์ ํŠธ ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ๊ถŒํ•œ์ด ์—†๋”๋ผ๋„ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•œ ๋ฌธ์ œ์ ์ด ๋ฐœ๊ฒฌ๋˜์—ˆ๋‹ค.

ํ”„๋กœ์ ํŠธ์˜ ๊ณ ์œ  ๋ฒˆํ˜ธ๊ฐ€ 630 ์ด๋ผ๊ณ  ํ•˜๋ฉด URL์— ์ง์ ‘ make/630 ๋“ฑ ํ”„๋กœ์ ํŠธ ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ๋‚ด ํ”„๋กœ์ ํŠธ๊ฐ€ ์•„๋‹ˆ์—ฌ๋„ ํŽธ์ง‘ ๋ฐ ์‹ค์‚ฌ ์ง„ํ–‰์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

โšก ์›์ธ
ํ”„๋กœ์ ํŠธ์— ๊ด€๋ จํ•˜์—ฌ ์ด ํ”„๋กœ์ ํŠธ๊ฐ€ ๋‚ด ํ”„๋กœ์ ํŠธ์ธ์ง€ ํ™•์ธํ•ด์ฃผ๋Š” ๊ฒ€์‚ฌ ๋กœ์ง์ด ์—†์—ˆ๋‹ค.
๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— URL๋กœ ์ง์ ‘ ์ž…๋ ฅํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด ๋ˆ„๊ตฌ๋‚˜ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ์ ์ด ์žˆ๋‹ค.

โš’๏ธ ํ•ด๊ฒฐ
- vue routes ์—์„œ ์ฒ˜๋ฆฌ
๋ผ์šฐํ„ฐ ์ด๋™ ์ „์— ๊ถŒํ•œ์„ ๊ฒ€์‚ฌํ•˜๋Š” ๋กœ์ง์„ ์งœ์„œ ์ ‘๊ทผ์„ ๋ง‰๋Š” ๋ฐฉ๋ฒ•

- Nest ์—์„œ Custom Guards ๋ฅผ ๋งŒ๋“ค์–ด์„œ ์ฒ˜๋ฆฌ
์š”์ฒญ์ด ์™”์„ ๊ฒฝ์šฐ ๋ฐฑ์—”๋“œ์—์„œ ๊ถŒํ•œ์„ ์กฐํšŒํ•œ๋‹ค.

์ด 2๊ฐ€์ง€ ์ค‘ ํ•˜๋‚˜๋งŒ ํ•ด์ค˜๋„ ๊ถŒํ•œ์ด ์—†๋Š” ํ”„๋กœ์ ํŠธ์— ์ ‘๊ทผ ํ•˜๋Š” ๊ฒƒ์„ ๋ง‰์„ ์ˆ˜ ์žˆ๋‹ค.

 

Vue Router Guard


// 1. ๋จผ์ € Vue Routes์— meta ์˜ต์…˜์„ ํ™œ์šฉํ•œ๋‹ค.
// EX: routes -> index.ts
Vue.use(VueRouter);
const routes: Array<RouteConfig> = [
    .
    .
    {
        path: '/login',
        name: 'login',
        component: Login,
        meta: { unauthorized: true },
    },
    .
    .
    .
    {
        path: '/project/sampling/:id',
        name: 'surveySampling',
        component: Sampling,
        meta: { projectAccess: true },
    },
    {
        path: '/project/make/:id',
        name: 'surveyMake',
        component: Make,
        meta: { projectAccess: true },
    },
    .
    .

 

meta ์†์„ฑ ๊ฐ’์ด true ์ผ ๊ฒฝ์šฐ ๊ฒ€์‚ฌ๋ฅผ ํ•˜๊ฒ ๋‹ค ๋ผ๋Š” ์˜๋ฏธ

 

์œ„์˜ ์†์„ฑ์„ ๊ฐ€์ง€๊ณ  rotuer.before ์—์„œ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

// router.beforeEach() ์กฐ๊ฑด ๊ฒ€์‚ฌ
router.beforeEach(async (to: Route, from, next) => {
    try {
        const { matched } = to;
        let verifyFlag = true;
        const isUnauthorized = matched.some((record) => record.meta.unauthorized);
        const { meta } = matched.find((record) => record.meta) || {};
        const { role }: any = meta || { role: '' };
        if (!isUnauthorized) {
            const { result } = await store.dispatch('verify');
            if (!result) {
                verifyFlag = false;
                return next('/login');
            }
        }
        if (verifyFlag === true && role === 'ADMIN') {
            if (!store.getters.isAdmin) {
            await app.$toast.error('๊ถŒํ•œ์ด ์—†๋Š” ํŽ˜์ด์ง€ ์ž…๋‹ˆ๋‹ค.');
            return next('/project/list');
            }
        }
        // MARK: ์ถ”๊ฐ€๋œ ํ”„๋กœ์ ํŠธ ๊ถŒํ•œ ๊ฒ€์‚ฌ
        const projectAccess = matched.some((record) => record.meta.projectAccess);
        if (projectAccess) {
          const { params } = to;
          const { id } = params;
          const { data } = await store.dispatch('projectAccess', id);
          const { result } = data;
          if (result) return next();
          else {
            await app.$toast.error('์ž˜๋ชป๋œ ์ ‘๊ทผ์ž…๋‹ˆ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.');
            return next('/project/list');
          }
        }
        return next();
    } catch (e) {
        console.error(e);
    }
});

๋ผ์šฐํŒ…์ด ๋  ๋•Œ meta ์†์„ฑ์ด ์žˆ๋Š”์ง€ ํ™•์ธ์„ ํ•˜๊ณ  ์žˆ์œผ๋ฉด ํ•ด๋‹นํ•˜๋Š” ๊ฐ’์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ฒŒ ๋œ๋‹ค.

 

project API ์˜ ๊ฒฝ์šฐ meta ์†์„ฑ์ด projectAccess ๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ์„ ์–ธ ๋˜์–ด์žˆ๋‹ค.

๋”ฐ๋ผ์„œ beforeEach ์—์„œ ๊ฒ€์‚ฌ๋ฅผ ํ†ต๊ณผํ•ด์•ผ ๋ Œ๋”๋ง ๋˜๋„๋ก ํ•œ๋‹ค.

 

๋ฐฑ์—”๋“œ์—์„œ ์œ„ ์กฐ๊ฑด์„ ๊ฒ€์‚ฌํ•˜๊ณ  ๊ทธ์— ๋งž๋Š” ๋ฐ˜ํ™˜๊ฐ’์„ ๋„˜๊ฒจ์ค€๋‹ค.

์ผ์น˜ํ•œ๋‹ค๋ฉด true๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ํ•˜์˜€๊ณ , ์ผ์น˜ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด false๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

 

๋ฐฑ์—”๋“œ๋กœ ํ†ต์‹ ์„ ํ•  ๋•Œ Axios Instance๋ฅผ ์‚ฌ์šฉํ•˜์˜€๊ธฐ ๋•Œ๋ฌธ์— ๋ฐฑ์—”๋“œ์—์„œ ๋„˜๊ฒจ์ง„ ๊ฐ’๋“ค์€ ์ œ์ผ ๋จผ์ € axios์—์„œ ๋ฐ›๊ฒŒ ๋˜์–ด์žˆ๋‹ค.

 

 

Vue Axios Instance Error Handling


๋ฐฑ์—”๋“œ์—์„œ ์ „๋‹ฌ ๋ฐ›์€ ๊ฐ’์œผ๋กœ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค.

// utils -> axios.ts (Custom Axios instance)
import axios from 'axios';
import store from '@/store/index';
import router from '@/router/index';
import Vue from 'vue';

const baseURL = '/api';
const instance = axios.create({
    baseURL,
    headers: {
        'Content-Type': 'application/json',
    },
    timeout: 20000,
});

// REQUEST (์š”์ฒญ)
instance.interceptors.request.use((config) => {
    if (store) {
        const { token } = store.getters;
        if (token) config.headers['Authorization'] = `Bearer ${token}`;
    }
    return config;
});

// RESPONSE (์‘๋‹ต)
instance.interceptors.response.use((response) => {
    return response;
    },
    (error) => {
        const { response } = error;
        const { data, status } = response;
        const { message } = data;
        if (status === 401) {
            return router.replace({ path: '/' }).catch(() => ({}));
        } else {
            Vue.$toast.error(message);
            console.log('debug-', data);
        }
    return response;
    }
);
export const ins = instance;

๋ฐฑ์—”๋“œ์—์„œ ์‘๋‹ต๊ฐ’์ด ์ œ๋Œ€๋กœ ์ „๋‹ฌ๋œ๋‹ค๋ฉด axios ์—์„œ onFulfilled(response)์— ์ „๋‹ฌ๋œ๋‹ค.

 

๋งŒ์•ฝ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒ๋œ๋‹ค๋ฉด onRejected(error)์— ์ „๋‹ฌ์ด ๋˜๋Š”๋ฐ ์ด๋ฅผ ๊ฐ€์ง€๊ณ  ์›ํ•˜๋Š” ํ˜•ํƒœ๋กœ ์—๋Ÿฌ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๋งˆ๋ฌด๋ฆฌ


์‚ฌ์‹ค ์ดˆ๊ธฐ์—๋Š” ๋ผ์šฐํŠธ๋ฅผ ์ด์šฉํ•˜์ง€ ์•Š๊ณ , ๋ฐฑ์—”๋“œ์—์„œ ๊ฐ€๋“œ๋ฅผ ๋งŒ๋“ค์–ด ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ƒ๊ฐ์ด์˜€๋‹ค.

 

ํ•˜์ง€๋งŒ... Nest์—์„œ ๊ฐ€๋“œ๋ฅผ ๋งŒ๋“ค์–ด ๋ณธ ๊ฒฝํ—˜์ด ์—†์–ด์„œ ์ฐพ์•„์„œ ๋งŒ๋“ค์–ด ๋ณด๋Š”๋ฐ ์‹œ๊ฐ„์ด ์˜ค๋ž˜๊ฑธ๋ฆด ๊ฒƒ ๊ฐ™์•„ ๊ธ‰ํ•œ๋Œ€๋กœ ํ”„๋ก ํŠธ์—์„œ ๋ง‰๋Š” ๋ฐฉ๋ฒ•์„ ์„ ํƒํ–ˆ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ๋ฌด์—‡๋ณด๋‹ค Router Guard์˜ ์—ญํ• ์€ ์•Œ๊ณ  ์žˆ์—ˆ์ง€๋งŒ ์ด๋ฒˆ์ฒ˜๋Ÿผ ์ œ๋Œ€๋กœ ๋‹ค๋ค„ ๋ณธ ์ ์€ ์—†์—ˆ๋‹ค.

 

์ด๋ฒˆ์— Router Guard์™€ axios๋ฅผ ์ง์ ‘ custom ํ•ด๋ณด๋ฉด์„œ ์ด ๋‘˜์„ ์ดํ•ดํ•˜๋Š”๋ฐ ํฐ ๋„์›€์ด ๋˜์—ˆ๋‹ค.

 

์ผ๋‹จ ์›ํ•˜๋Š” ๊ธฐ๋Šฅ์€ ๊ตฌํ˜„ํ•˜์˜€์œผ๋‹ˆ Nest Guard๋Š” ๊ตฌํ˜„์ด ๋˜๋Š”๋Œ€๋กœ ์ •๋ฆฌํ•ด ๋ณด์ž.

 

 


728x90
๋ฐ˜์‘ํ˜•