하관우 하관우 03-19
2025-03-19 하관우 1차 로그인인
@e0cdf009db4bcf473126db75d78c37331dc9620b
 
client/resources/api/index.js (added)
+++ client/resources/api/index.js
@@ -0,0 +1,74 @@
+import axios from "axios";
+import store from "../../views/pages/AppStore";
+
+const apiClient = axios.create({
+    headers: {'Content-Type': 'application/json; charset=UTF-8'}
+})
+
+apiClient.interceptors.request.use(
+    config => {
+        config.headers.Authorization = store.state.authorization // 요청 시 AccessToken 추가
+        return config;
+    },
+    error => {
+        return Promise.reject(error);
+    }
+)
+
+async function refreshAccessToken() {
+    try {
+        const refreshToken = store.state.refreshToken; // 스토어에서 리프레시 토큰 가져오기
+        
+        // 리프레시 토큰을 포함하여 재발급 요청
+        const res = await axios.post('/refresh/tknReissue.json', {
+            refreshToken: refreshToken // 리프레시 토큰 본문에 포함
+        });
+
+        if (res.headers.authorization) {
+            // 새로 발급받은 AccessToken 저장
+            store.commit('setAuthorization', res.headers.authorization);
+            // 필요한 경우 리프레시 토큰도 업데이트
+            // store.commit('setRefresh', res.headers.refresh);
+        }
+
+        return res; // 응답 반환
+    } catch (error) {
+        console.error('Error refreshing access token:', error);
+        alert('토큰 재발급에 실패했습니다. 다시 로그인 해주세요.');
+        store.commit("setStoreReset");
+        window.location = '/login.page'; // 로그인 페이지로 리다이렉트
+    }
+}
+
+apiClient.interceptors.response.use(
+    response => {
+        return response;
+    },
+    async error => {
+        const originalReq = error.config;
+
+        // 403 오류 처리: 접근 권한 없음
+        if (error.response.status === 403 && error.response.data.message === '접근 권한이 없습니다.') {
+            window.history.back();
+            return Promise.reject(error);
+        }
+
+        // 401 오류 처리: 토큰 만료
+        if (error.response.status === 401 && !originalReq._retry) {
+            originalReq._retry = true; // 재요청 시도 플래그 설정
+            try {
+                // 액세스 토큰 재발급 요청
+                await refreshAccessToken(); // 리프레시 함수 호출
+
+                // 원래 요청 재시도
+                return apiClient(originalReq);
+            } catch (refreshError) {
+                return Promise.reject(refreshError);
+            }
+        }
+
+        return Promise.reject(error);
+    }
+)
+
+export default apiClient;
 
client/resources/api/logOut.js (added)
+++ client/resources/api/logOut.js
@@ -0,0 +1,10 @@
+import apiClient from "./index";
+import store from '../../views/pages/AppStore';
+
+export const logOutProc = () => {
+    return apiClient.post(`/user/logout.json`, {}, {
+        headers: {
+            'refresh': store.state.refresh
+        }
+    });
+}(파일 끝에 줄바꿈 문자 없음)
 
client/resources/api/login.js (added)
+++ client/resources/api/login.js
@@ -0,0 +1,5 @@
+import apiClient from "./index";
+
+export const loginProc = mber => {
+    return apiClient.post(`/user/login.json`, mber);
+}(파일 끝에 줄바꿈 문자 없음)
client/views/pages/AppRouter.js
--- client/views/pages/AppRouter.js
+++ client/views/pages/AppRouter.js
@@ -1,4 +1,5 @@
 import { createWebHistory, createRouter } from "vue-router";
+import store from "./AppStore";
 
 // 공통페이지
 import Login from "./login/Login.vue";
@@ -24,6 +25,21 @@
 
 AppRouter.beforeEach((to, from, next) => {
   const routeExists = AppRouter.getRoutes().some(route => route.path === to.path || (route.name && route.name === to.name));
+  const authorState = store.state.roles;
+  const { authorization } = to.meta;
+
+  // 토큰이 없을 경우
+  if (authorState == null && to.path != '/login.page') {
+    alert('로그인이 필요합니다.');
+    return next('/login.page');
+  }
+  // 권한이 없을 경우
+  if(authorization){
+    if(!authorization.includes(authorState[0].authority)){
+      alert('접근 권한이 없습니다.');
+      return next(from.path);
+    }
+  }
   if (!routeExists) {
     next({ name: 'NotFoundPage' });
     return;
client/views/pages/AppStore.js
--- client/views/pages/AppStore.js
+++ client/views/pages/AppStore.js
@@ -1,10 +1,63 @@
 import { createStore } from "vuex";
 import createPersistedState from "vuex-persistedstate";
+import { logOutProc } from "../../resources/api/logOut";
 
 export default createStore({
   plugins: [createPersistedState()],
-  state: {},
-  getters: {},
-  mutations: {},
-  actions: {},
+  state: {
+    authorization: null,
+    // refresh: null,
+    roles: [{authority: "ROLE_USER"}],
+  },
+  getters: {
+    getAuthorization: function () {},
+    // getRefresh: function () {},
+    getUserNm: function () {},
+    getRoles: function () {},
+  },
+  mutations: {
+    setAuthorization(state, newValue) {
+      state.authorization = newValue;
+    },
+    // setRefresh(state, newValue) {
+    //   state.refresh = newValue;
+    // },
+    setUserNm(state, newValue) {
+      state.userNm = newValue;
+    },
+    setUserId(state, newValue) {
+      state.userId = newValue;
+    },
+    setRoles(state, newValue) {
+      state.roles = newValue;
+    },
+    setStoreReset(state) {
+      state.authorization = null;
+      // state.refresh = null;
+      state.userNm = null;
+      state.userId = null;
+      state.roles = [{authority: "ROLE_USER"}];
+    },
+  },
+  actions: {
+    async logout({ commit }) {
+      try {
+        const res = await logOutProc();
+        alert(res.data.message);
+        if (res.status == 200) {
+          commit("setStoreReset");
+        }
+      } catch(error) {
+        const errorData = error.response.data;
+        if (errorData.message != null && errorData.message != "") {
+          alert(error.response.data.message);
+        } else {
+          alert("에러가 발생했습니다.\n관리자에게 문의해주세요.");
+        }
+      }
+    },
+    setStoreReset({commit}) {
+      commit("setStoreReset");
+    }
+  },
 });
(파일 끝에 줄바꿈 문자 없음)
client/views/pages/login/Login.vue
--- client/views/pages/login/Login.vue
+++ client/views/pages/login/Login.vue
@@ -1,6 +1,6 @@
 <template>
     <div class="content">
-      <div class="sub-title-area mb-110">
+        <div class="sub-title-area mb-110">
             <h2>로그인</h2>
             <div class="breadcrumb-list">
                 <ul>
@@ -10,17 +10,18 @@
                     <li>로그인</li>
                 </ul>
             </div>
-      </div>
+        </div>
         <form action="login" class="login-form">
             <dl>
                 <dd class="mb-25">
                     <label for="id">아이디</label>
-                    <input type="text" id="id" class="wfull" placeholder="아이디를 입력하세요.">
+                    <input type="text" id="id" class="wfull" placeholder="아이디를 입력하세요." v-model="member['loginId']">
                 </dd>
-                
+
                 <dd class="mb-10">
                     <label for="pw">비밀번호</label>
-                    <input type="text" id="pw" class="wfull" placeholder="비밀번호를 입력하세요.">
+                    <input type="password" id="pw" class="wfull" placeholder="비밀번호를 입력하세요." v-model="member['password']"
+                        @keyup.enter="fnLogin">
                 </dd>
                 <dd class="check-area flex-end mb-25">
                     <input type="checkbox" class="margin-top">
@@ -28,20 +29,78 @@
                 </dd>
             </dl>
             <!-- Bind the image source dynamically for loginicon -->
-            <button><img :src="loginicon" alt="Login Icon"><span>로그인</span></button>
+            <button type="button" @click="fnLogin"><img :src="loginicon" alt="Login Icon"><span>로그인</span></button>
         </form>
     </div>
 </template>
 
 <script>
+import { useStore } from "vuex";
+import { loginProc } from "../../../resources/api/login";
+import axios from "axios";
+
 export default {
-  data() {
-    return {
-      // Define the image sources
-      homeicon: 'client/resources/images/icon/home.png',
-      loginicon: 'client/resources/images/icon/lock.png',
-      righticon: 'client/resources/images/icon/right.png',
-    };
-  }
+    data() {
+        return {
+            // Define the image sources
+            homeicon: 'client/resources/images/icon/home.png',
+            loginicon: 'client/resources/images/icon/lock.png',
+            righticon: 'client/resources/images/icon/right.png',
+
+            member: {
+                loginId: null,
+                password: null,
+            },
+            store: useStore(),
+        };
+    },
+    methods: {
+        async fnLogin() {
+            try {
+                const response = await axios.post("/user/login.json", this.member, {
+                    headers: {
+                        "Content-Type": "application/json; charset=UTF-8",
+                    },
+                });
+
+                console.log(response); // 응답 확인
+
+                if (response.status === 200) {
+                    // 토큰 저장 로직
+                    this.$store.commit("setAuthorization", response.headers.authorization);
+                    /** jwt토큰 복호화 **/
+                    const base64String = this.$store.state.authorization.split(".")[1];
+                    const base64 = base64String.replace(/-/g, "+").replace(/_/g, "/");
+                    const jsonPayload = decodeURIComponent(
+                        atob(base64)
+                            .split("")
+                            .map((c) => {
+                                return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
+                            })
+                            .join("")
+                    );
+                    const mbr = JSON.parse(jsonPayload);
+                    //const mbr = JSON.parse(decodeURIComponent(escape(window.atob(base64String)))); // jwt claim 추출
+                    console.log("리멤버미~", mbr);
+                    this.$store.commit("setUserId", mbr.userId);
+                    this.$store.commit("setUserNm", mbr.userNm);
+                    this.$store.commit("setRoles", mbr.roles);
+                    /** jwt토큰 복호화 **/
+
+                    // 리다이렉트 처리
+                    this.$router.push("/");
+                }
+            } catch (error) {
+                console.error("Login error:", error); // 에러 로그
+                const message = error.response?.data?.message || "로그인에 실패했습니다.";
+                alert(message);
+            }
+        }
+    },
+    watch: {},
+    computed: {},
+    components: {},
+    created() { },
+    mounted() { },
 };
 </script>
Add a comment
List