Syw.Frontend

๐Ÿงฉ Vue 2๋กœ ๋ฐฐ์šฐ๋Š” ํ”„๋ก ํŠธ์—”๋“œ ๊ธฐ์ดˆ

1๋‹จ๊ณ„. Vue 2 + Express + Axios ์ธ์ฆ ๊ธฐ๋ฐ˜ ํˆฌ๋‘์•ฑ ๋งŒ๋“ค๊ธฐ

1-19. JWT ํ† ํฐ ๋งŒ๋ฃŒ ์‹œ ์ž๋™ ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌํ•˜๊ธฐ

๐ŸŽฏ ํ•™์Šต ๋ชฉํ‘œ

  • JWT ํ† ํฐ์— ์„ค์ •๋œ exp(๋งŒ๋ฃŒ์‹œ๊ฐ„)๋ฅผ ํŒŒ์‹ฑํ•˜์—ฌ ํ™•์ธ
  • ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์—ˆ์œผ๋ฉด ์ž๋™์œผ๋กœ ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ ๋ฐ ์•Œ๋ฆผ
  • ์•ฑ์ด ์‹œ์ž‘๋˜๊ฑฐ๋‚˜ ํŽ˜์ด์ง€ ์ด๋™ ์‹œ๋งˆ๋‹ค ๋งŒ๋ฃŒ ์—ฌ๋ถ€ ๊ฒ€์‚ฌ

โœ… 1. JWT ํ† ํฐ์—์„œ ๋งŒ๋ฃŒ์‹œ๊ฐ„(exp) ์ฝ๋Š” ํ•จ์ˆ˜ ์ž‘์„ฑ

๐Ÿ“ src/utils/jwt.js ์ƒ์„ฑ

export function getTokenPayload(token) {
  try {
    const payload = token.split('.')[1];
    const decoded = atob(payload);
    return JSON.parse(decoded); // { exp, username, ... }
  } catch {
    return null;
  }
}

export function isTokenExpired(token) {
  const payload = getTokenPayload(token);
  if (!payload || !payload.exp) return true; // ์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ์€ ๋งŒ๋ฃŒ ์ฒ˜๋ฆฌ

  const now = Math.floor(Date.now() / 1000); // ํ˜„์žฌ ์‹œ๊ฐ (์ดˆ ๋‹จ์œ„)
  return payload.exp < now; // true๋ฉด ๋งŒ๋ฃŒ๋จ
}

โœ… 2. Vuex ์Šคํ† ์–ด์—์„œ ๋งŒ๋ฃŒ ํ™•์ธ ๋ฐ ์ž๋™ ๋กœ๊ทธ์•„์›ƒ ์ถ”๊ฐ€

๐Ÿ”ง store/index.js ์ˆ˜์ •

import Vue from 'vue';
import Vuex from 'vuex';
import { isTokenExpired } from '../utils/jwt';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    isDark: localStorage.getItem('theme') === 'dark',
    isLoggedIn: false,
  },
  mutations: {
    toggleDark(state) {
      state.isDark = !state.isDark;
      localStorage.setItem('theme', state.isDark ? 'dark' : 'light');
    },
    login(state, token) {
      localStorage.setItem('token', token);
      state.isLoggedIn = true;
    },
    logout(state) {
      localStorage.removeItem('token');
      state.isLoggedIn = false;
    },
    syncLoginState(state) {
      const token = localStorage.getItem('token');
      if (!token || isTokenExpired(token)) {
        localStorage.removeItem('token');
        state.isLoggedIn = false;
      } else {
        state.isLoggedIn = true;
      }
    },
  },
});

syncLoginState() ๋‚ด๋ถ€์—์„œ ํ† ํฐ ๋งŒ๋ฃŒ ์—ฌ๋ถ€๋ฅผ ๊ฒ€์‚ฌํ•˜๊ณ , ๋งŒ๋ฃŒ๋˜์—ˆ์œผ๋ฉด ์ž๋™ ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.


โœ… 3. App.vue์—์„œ ์•ฑ ์‹œ์ž‘ ์‹œ ๋™๊ธฐํ™” ํ˜ธ์ถœ

created() {
  this.syncLoginState();
}

โœ… 4. ๋ผ์šฐํ„ฐ ์ด๋™ ์‹œ ํ† ํฐ ๋งŒ๋ฃŒ๋„ ์ฒดํฌ

๐Ÿ”ง router.js

import store from './store';

router.beforeEach((to, from, next) => {
  store.commit('syncLoginState'); // โœ… ์ด๋™ ์ „๋งˆ๋‹ค ๋กœ๊ทธ์ธ ์ƒํƒœ ๋™๊ธฐํ™”

  if (to.matched.some((record) => record.meta.requiresAuth)) {
    if (!store.state.isLoggedIn) {
      alert('์„ธ์…˜์ด ๋งŒ๋ฃŒ๋˜์—ˆ๊ฑฐ๋‚˜ ๋กœ๊ทธ์ธ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.');
      next('/login');
    } else {
      next();
    }
  } else {
    next();
  }
});

๐Ÿ“ฆ ์š”์•ฝ

์ด ๊ฐ•์˜์—์„œ๋Š” JWT์˜ exp ๊ฐ’์„ ๊ฒ€์‚ฌํ•ด ํ† ํฐ ๋งŒ๋ฃŒ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๊ณ , ๋งŒ๋ฃŒ๋œ ๊ฒฝ์šฐ ์ž๋™ ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋ณด์•ˆ๊ณผ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๋™์‹œ์— ๊ฐœ์„ ํ–ˆ์Šต๋‹ˆ๋‹ค.

GitHub - heroyooi/vue2-app at ch19

๐Ÿ’ฌ ๋Œ“๊ธ€

    โ€ป ๋กœ๊ทธ์ธ ํ›„ ๋Œ“๊ธ€์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.