hmkim 05-29
250529 김혜민 시스템, 소셜 통합로그인처리
@a8188be3f09f2b96fc7d6a2861cf9f983c54fc2d
client/views/pages/AppRouter.js
--- client/views/pages/AppRouter.js
+++ client/views/pages/AppRouter.js
@@ -167,128 +167,145 @@
     routes: newRoutes,
   });
 
-  AppRouter.beforeEach(async (to, from, next) => {
-    const contextPath = store.state.contextPath; // Context Path 정보
-    const admPath = to.path.includes('/adm'); // 관리자 페이지 여부 (true: 관리자 페이지, false: 사용자 페이지)
-    // const routeExists = AppRouter.getRoutes().some(route => route.path === to.path || (route.name && route.name === to.name));
-    // if (!routeExists) {
-    //   next({ name: 'notfound' });
-    //   return;
-    // }
-    // vue3 권장방식 ('/:pathMatch(.*)*'로 라우터 유무 확인)
-    if(to.name === 'PageNotFound') {
-      next();
-      return;
-    }
+ AppRouter.beforeEach(async (to, from, next) => {
+  const contextPath = store.state.contextPath; // Context Path 정보
+  const admPath = to.path.includes('/adm'); // 관리자 페이지 여부 (true: 관리자 페이지, false: 사용자 페이지)
+  
+  if(to.name === 'PageNotFound') {
+    next();
+    return;
+  }
 
-    // 로그인 모드 확인 (JWT 또는 SESSION)
-    const loginMode = store.state.loginMode || 'J'; // 기본값으로 JWT 설정
-    // 로그인 상태 확인 (JWT 또는 SESSION)
-    const isLogin = loginMode === 'J' ? store.state.authorization : store.state.mbrId;
-    // if (!isLogin && to.path !== filters.ctxPath('/login.page')) {
-    //   sessionStorage.setItem('redirect', to.fullPath); 
-    //   next({ path: filters.ctxPath("/login.page") });
-    //   return; 
-    // }
+  // 로그인 모드 확인 개선
+  let loginMode = store.state.loginMode || localStorage.getItem('loginMode') || 'J'; // 기본값으로 JWT 설정
+  
+  // 로그인 모드가 여전히 없으면 localStorage에서 다시 한번 확인
+  if (!loginMode || loginMode === 'undefined') {
+    loginMode = 'J';
+  }
 
-    // 접근 제어 확인
-    const accesCheck = await accessUrl(to.path);
-    const roleCheck = isValidRole();
-    if (!accesCheck || !roleCheck) {
-      alert('접근이 불가합니다.\n관리자에게 문의하세요.');
-      next(filters.ctxPath('/'));
-    }
-    // 경로에 따른 사용자 타입 설정
-    if (to.path === filters.ctxPath('/')) {
-      store.commit('setUserType', 'portal')
-    } else if (to.path.startsWith(filters.ctxPath('/adm'))) {
-      store.commit('setUserType', 'adm');
-    } 
+  // 로그인 상태 확인 개선
+  let isLogin = false;
+  if (loginMode === 'J') {
+    // JWT 모드: authorization 토큰 확인
+    const token = store.state.authorization || localStorage.getItem('authorization');
+    isLogin = !!token;
+  } else if (loginMode === 'S') {
+    // 세션 모드: mbrId 확인
+    const mbrId = store.state.mbrId || localStorage.getItem('mbrId');
+    isLogin = !!mbrId;
+  }
 
-    if (to.path === filters.ctxPath('/login.page')) {
-      store.commit('setPath', to.path);
-      next();
-      return;
+  // OAuth2 콜백 처리 - 로그인 페이지에서만 처리
+  if (to.path.includes('/login.page') && (to.query.oauth_success || to.query.error)) {
+    next();
+    return;
+  }
+
+  // 접근 제어 확인
+  const accesCheck = await accessUrl(to.path);
+  const roleCheck = isValidRole();
+  if (!accesCheck || !roleCheck) {
+    alert('접근이 불가합니다.\n관리자에게 문의하세요.');
+    next(filters.ctxPath('/'));
+    return; 
+  }
+  
+  // 경로에 따른 사용자 타입 설정
+  if (to.path === filters.ctxPath('/')) {
+    store.commit('setUserType', 'portal')
+  } else if (to.path.startsWith(filters.ctxPath('/adm'))) {
+    store.commit('setUserType', 'adm');
+  } 
+
+  if (to.path === filters.ctxPath('/login.page')) {
+    store.commit('setPath', to.path);
+    next();
+    return;
+  }
+  
+  const mbrAuth = store.state.roles.map(auth => auth.authority); // 사용자 권한 정보
+  const pageAuth = mergeAuth(mbrAuth, to.meta);
+  sessionStorage.setItem("redirect", to.fullPath);
+  
+  // 메인 페이지 or 로그인 페이지
+  if (to.path === filters.ctxPath('/') || to.path.includes('/login.page') || to.path.includes('/cmslogin.page') || to.path.startsWith(filters.ctxPath('/cmmn/')) || to.path.includes('/searchId.page') || to.path.includes('/resetPswd.page')) {
+    let path = to.path;
+    // 게시판일 경우 .page로 끝나는 경로가 있으므로 마지막 '/' 이전 경로로 설정
+    if (to.path.includes('BBS_MNG')) {
+      let logicalPath = to.path;
+      // context path 제거
+      if (contextPath !== '/' && logicalPath.startsWith(contextPath)) {
+        logicalPath = logicalPath.substring(contextPath.length);
+      }
+      const lastSlashIndex = logicalPath.lastIndexOf('/'); // 마지막 '/' 인덱스
+      path = logicalPath.substring(0, lastSlashIndex); // 마지막 '/' 이전 경로
     }
-    const mbrAuth = store.state.roles.map(auth => auth.authority); // 사용자 권한 정보
-    const pageAuth = mergeAuth(mbrAuth, to.meta);
-    sessionStorage.setItem("redirect", to.fullPath);
-    
-    // 메인 페이지 or 로그인 페이지
-    if (to.path === filters.ctxPath('/') || to.path.includes('/login.page') || to.path.includes('/cmslogin.page') || to.path.startsWith(filters.ctxPath('/cmmn/')) || to.path.includes('/searchId.page') || to.path.includes('/resetPswd.page')) {
-      let path = to.path;
-      // 게시판일 경우 .page로 끝나는 경로가 있으므로 마지막 '/' 이전 경로로 설정
-      if (to.path.includes('BBS_MNG')) {
+    store.commit('setPath', path);
+    store.commit('setPageAuth', pageAuth);
+    if (to.path === filters.ctxPath('/') || path.includes('/main.page')) {
+      await cntnStatsSave(null, mbrAuth); // 메인 페이지 접속 시 사용자 접속 통계 증가
+    }
+    next();
+  } else if (isLogin) {
+    // 로그인 상태이고, 권한이 허용인 경우 검사
+    const hasAcc = to.matched.some(record => {
+      if (!record.meta.authrt) return false;
+      return record.meta.authrt.some(auth => {
+        // 경로별 권한 검사
+        if (to.path.includes('/list.page')) { // 목록 권한 검증
+          return mbrAuth.includes(auth.authrtCd) && auth.inqAuthrt === 'Y';
+        } else if (to.path.includes('/insert.page')) { // 등록 및 수정 권한 검증
+          return mbrAuth.includes(auth.authrtCd) && auth.regAuthrt === 'Y';
+        } else if (to.path.includes('/view.page')) { // 상세조회 권한 검증
+          return mbrAuth.includes(auth.authrtCd) && auth.inqAuthrt === 'Y';
+        } else if (to.path.includes('/main.page')) { // 메인 페이지 권한 검증
+          return mbrAuth.includes(auth.authrtCd) && auth.inqAuthrt === 'Y';
+        } else if (to.path.includes('/search.page')) {
+          return mbrAuth.includes(auth.authrtCd) && auth.inqAuthrt === 'Y';
+        } else {
+          return false;
+        }
+      });
+    });
+    // 권한이 있고 접근 가능한 경우
+    if (hasAcc) {
+      if (to.path.includes('.page')) {
         let logicalPath = to.path;
         // context path 제거
         if (contextPath !== '/' && logicalPath.startsWith(contextPath)) {
           logicalPath = logicalPath.substring(contextPath.length);
         }
         const lastSlashIndex = logicalPath.lastIndexOf('/'); // 마지막 '/' 인덱스
-        path = logicalPath.substring(0, lastSlashIndex); // 마지막 '/' 이전 경로
+        const path = logicalPath.substring(0, lastSlashIndex); // 마지막 '/' 이전 경로
+        store.commit('setPath', path);
       }
-      store.commit('setPath', path);
-      store.commit('setPageAuth', pageAuth);
-      if (to.path === filters.ctxPath('/') || path.includes('/main.page')) {
+      // 접속 통계
+      if (to.path.includes('/main.page')) {
         await cntnStatsSave(null, mbrAuth); // 메인 페이지 접속 시 사용자 접속 통계 증가
-      }
-      next();
-    } else if (isLogin) {
-      // 로그인 상태이고, 권한이 허용인 경우 검사
-      // const hasAcc = true;
-      const hasAcc = to.matched.some(record => {
-        if (!record.meta.authrt) return false;
-        return record.meta.authrt.some(auth => {
-          // 경로별 권한 검사
-          if (to.path.includes('/list.page')) { // 목록 권한 검증
-            return mbrAuth.includes(auth.authrtCd) && auth.inqAuthrt === 'Y';
-          } else if (to.path.includes('/insert.page')) { // 등록 및 수정 권한 검증
-            return mbrAuth.includes(auth.authrtCd) && auth.regAuthrt === 'Y';
-          } else if (to.path.includes('/view.page')) { // 상세조회 권한 검증
-            return mbrAuth.includes(auth.authrtCd) && auth.inqAuthrt === 'Y';
-          } else if (to.path.includes('/main.page')) { // 메인 페이지 권한 검증
-            return mbrAuth.includes(auth.authrtCd) && auth.inqAuthrt === 'Y';
-          } else if (to.path.includes('/search.page')) {
-            return mbrAuth.includes(auth.authrtCd) && auth.inqAuthrt === 'Y';
-          } else {
-            return false;
-          }
-        });
-      });
-      // 권한이 있고 접근 가능한 경우
-      if (hasAcc) {
-        if (to.path.includes('.page')) {
-          let logicalPath = to.path;
-          // context path 제거
-          if (contextPath !== '/' && logicalPath.startsWith(contextPath)) {
-            logicalPath = logicalPath.substring(contextPath.length);
-          }
-          const lastSlashIndex = logicalPath.lastIndexOf('/'); // 마지막 '/' 인덱스
-          const path = logicalPath.substring(0, lastSlashIndex); // 마지막 '/' 이전 경로
-          store.commit('setPath', path);
-        }
-        // 접속 통계
-        if (to.path.includes('/main.page')) {
-          await cntnStatsSave(null, mbrAuth); // 메인 페이지 접속 시 사용자 접속 통계 증가
-        } else {
-          if (!to.meta.typeId.includes('BBS_MNG') || to.path.includes('/list.page')) {
-            await cntnStatsSave(to.meta.typeId, mbrAuth);
-          }
-        }
-        store.commit('setPageAuth', pageAuth);
-        next();
-        // 권한이 없는 경우 이전 페이지 or / 이동
       } else {
-        alert('접근 권한이 없습니다.');
-        window.history.back();
-        // next(from.fullPath ? from.fullPath : '/');
+        if (!to.meta.typeId.includes('BBS_MNG') || to.path.includes('/list.page')) {
+          await cntnStatsSave(to.meta.typeId, mbrAuth);
+        }
       }
+      store.commit('setPageAuth', pageAuth);
+      next();
+      // 권한이 없는 경우 이전 페이지 or / 이동
     } else {
-      if(admPath) {
-        // sessionStorage.setItem("redirect", to.fullPath);
-        next({ path: filters.ctxPath("/cmslogin.page") });
-      }
+      alert('접근 권한이 없습니다.');
+      window.history.back();
     }
-  });
+  } else {
+    // 관리자 로그인이 필요한 경우
+    if(admPath) {
+      sessionStorage.setItem("redirect", to.fullPath);
+      next({ path: filters.ctxPath("/cmslogin.page") });
+    } else {
+      // 일반 사용자 페이지에서 로그인이 필요한 경우
+      sessionStorage.setItem("redirect", to.fullPath);
+      next({ path: filters.ctxPath("/login.page") });
+    }
+  }
+});
   return AppRouter;
 }
(파일 끝에 줄바꿈 문자 없음)
client/views/pages/AppStore.js
--- client/views/pages/AppStore.js
+++ client/views/pages/AppStore.js
@@ -90,29 +90,25 @@
   },
   actions: {
     async logout({ commit }) {
-      try {
+       try {
         const ctx = this.state.contextPath; // 캐시 초기화 전 contextPath 저장
         const admPath = this.state.path?.includes("/adm") // 캐시 초기화 전 경로 구분 (true: 관리자 페이지, false: 사용자 페이지)
-        const loginMode = this.state.loginMode; // 로그인 모드 확인
-        
-        console.log("로그아웃 처리 시작 - 로그인 모드:", loginMode);
+        const loginMode = this.state.loginMode || localStorage.getItem('loginMode') || 'J'; 
         
         // 로그인 모드에 따른 처리
         if (loginMode === 'J') {
           // JWT 방식인 경우만 서버 API 호출
           try {
             const res = await logOutProc();
-            alert(res.data.message);
+            if (res.data.message) {
+              alert(res.data.message);
+            }
           } catch (error) {
-            console.log("JWT 로그아웃 API 에러, 클라이언트만 정리:", error);
+            console.log(error);
             // API 에러가 발생해도 클라이언트는 정리
           }
-        } else {
-          // 세션 방식 (OAuth 포함)은 서버 API 호출 없이 클라이언트만 정리
-          console.log("세션 방식 로그아웃 - 클라이언트만 정리");
         }
         
-        // 공통 클라이언트 정리 작업
         // 1. 상태 초기화
         commit("setStoreReset");
         
@@ -120,20 +116,29 @@
         localStorage.clear();
         sessionStorage.clear();
 
-        // 3. 쿠키 삭제
-        document.cookie = "refresh=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
-        document.cookie = "Authorization=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
-        document.cookie = "JSESSIONID=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
+        // 3. 모든 가능한 쿠키 삭제 (OAuth 관련 포함)
+        const cookiesToDelete = [
+          'refresh', 
+          'Authorization', 
+          'JSESSIONID', 
+          'oauth_access_token',  // OAuth 토큰 쿠키
+          'oauth_refresh_token', // OAuth 리프레시 토큰
+          'SESSION'              // 스프링 기본 세션 쿠키
+        ];
         
-        console.log("로그아웃 완료");
+        cookiesToDelete.forEach(cookieName => {
+          document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
+          document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${window.location.hostname};`;
+        });
         
-        // 4. 로그인 페이지로 이동
-        if(admPath) {
-          window.location = ctx + "/cmslogin.page";
-        } else {
-          // window.location = ctx + "/login.page";
-          window.location = ctx + "/";
-        }
+        // 4. 로그인 페이지로 이동 (OAuth 관련 파라미터 제거)
+        const cleanUrl = admPath ? 
+          ctx + "/cmslogin.page" : 
+          ctx + "/login.page";
+          
+        // URL에서 OAuth 관련 파라미터 제거
+        window.history.replaceState({}, document.title, cleanUrl);
+        window.location.href = cleanUrl;
         
       } catch(error) {
         console.error("로그아웃 처리 중 오류:", error);
@@ -151,15 +156,15 @@
         if (errorData?.message) {
           alert(errorData.message);
         } else {
-         
+          console.log("로그아웃 처리 중 예상치 못한 오류 발생");
         }
         
         // 로그인 페이지로 이동
-        if(admPath) {
-          window.location = ctx + "/cmslogin.page";
-        } else {
-          window.location = ctx + "/login.page";
-        }
+        const cleanUrl = admPath ? 
+          ctx + "/cmslogin.page" : 
+          ctx + "/login.page";
+          
+        window.location.href = cleanUrl;
       }
     },
     setUserType({ commit }, userType) {
client/views/pages/login/Login.vue
--- client/views/pages/login/Login.vue
+++ client/views/pages/login/Login.vue
@@ -7,64 +7,58 @@
           로그인하세요.
         </p>
       </div>
+
+      <!-- 일반 로그인 화면 -->
       <div class="login-wrap" v-if="!loginStep1 && !loginStep2">
         <div class="login">
-          <div :class="{
-            'login-title': true,
-            'user-login': !isAdminPage,
-          }">
+          <div :class="{ 'login-title': true, 'user-login': !isAdminPage }">
             LOGIN
           </div>
+
+          <!-- 아이디/비밀번호 입력 -->
           <div class="form-group">
             <label for="id" class="login-label">아이디</label>
-            <input type="text" name="" id="id" class="form-control md" placeholder="아이디를 입력하세요"
-              v-model="member['lgnId']" />
+            <input type="text" id="id" class="form-control md" placeholder="아이디를 입력하세요" v-model="member.lgnId" />
           </div>
           <div class="form-group">
             <label for="pw" class="login-label">비밀번호</label>
-            <input type="password" name="" id="pw" class="form-control md" placeholder="비밀번호를 입력하세요"
-              v-model="member['pswd']" @keydown.enter="fnLogin" />
+            <input type="password" id="pw" class="form-control md" placeholder="비밀번호를 입력하세요" 
+                   v-model="member.pswd" @keydown.enter="fnLogin" />
           </div>
-          <button class="btn md main user-btn" v-if="!isAdminPage" @click="fnLogin" @keydown.enter="fnLogin">
-            로그인
-          </button>
-          <button class="btn md main" v-else @click="fnLogin" @keydown.enter="fnLogin">
+
+          <!-- 로그인 버튼 -->
+          <button class="btn md main" :class="{ 'user-btn': !isAdminPage }" @click="fnLogin" @keydown.enter="fnLogin">
             로그인
           </button>
 
-          <div class="input-group" v-if="!isAdminPage">
-            <p class="pl10 pr10 cursor" @click="moveSearchId">아이디찾기</p>
-            <p class="pl10 pr0 cursor" @click="moveResetPswd">비밀번호 초기화</p>
-          </div>
-
-          <!-- OAuth2 로그인 버튼들 -->
+          <!-- 사용자 페이지 전용 기능 -->
           <div v-if="!isAdminPage">
-            <div>
-              <span>또는</span>
+            <!-- 아이디/비밀번호 찾기 -->
+            <div class="input-group">
+              <p class="pl10 pr10 cursor" @click="moveSearchId">아이디찾기</p>
+              <p class="pl10 pr0 cursor" @click="moveResetPswd">비밀번호 초기화</p>
             </div>
+
+            <!-- OAuth2 소셜 로그인 -->
             <div>
-              <button @click="fnOAuthLogin('kakao')" :disabled="isOAuthLoading">
-                카카오로 로그인
-              </button>
-
-              <button @click="fnOAuthLogin('naver')" :disabled="isOAuthLoading">
-                네이버로 로그인
-              </button>
-
-              <button @click="fnOAuthLogin('google')" :disabled="isOAuthLoading">
-                구글로 로그인
-              </button>
+              <div><span>또는</span></div>
+              <div>
+                <button @click="fnOAuthLogin('kakao')" :disabled="isOAuthLoading">카카오로 로그인</button>
+                <button @click="fnOAuthLogin('naver')" :disabled="isOAuthLoading">네이버로 로그인</button>
+                <button @click="fnOAuthLogin('google')" :disabled="isOAuthLoading">구글로 로그인</button>
+              </div>
             </div>
           </div>
         </div>
       </div>
+
+      <!-- 2차 인증 화면 -->
       <div class="login-wrap" v-else-if="loginStep1 && !loginStep2">
         <div>
           <p>인증코드 입력</p>
-        </div>
-        <div>
           <p>{{memberInfo.email}}로 전송된 6자리 인증코드를 입력하세요.</p>
-          <input type="text" class="form-control md" ref="code" @input="inputCode" v-model="memberInfo.code" maxlength="6" placeholder="인증코드를 입력하세요."/>
+          <input type="text" class="form-control md" ref="code" @input="inputCode" 
+                 v-model="memberInfo.code" maxlength="6" placeholder="인증코드를 입력하세요." />
         </div>
         <div class="btn-wrap">
           <button class="btn sm main" @click="fnCheck">인증코드 확인</button>
@@ -95,14 +89,7 @@
       store: useStore(),
       isAdminPage: false,
       isOAuthLoading: false,
-      // oauthProviders: [
-      //   { name: 'kakao', label: '카카오로 로그인' },
-      //   { name: 'naver', label: '네이버로 로그인' },
-      //   { name: 'google', label: '구글로 로그인' }
-      // ]
-
       memberInfo: { email: '', code: '' },
-      // 인증 절차
       loginStep1: false, // 1차 인증
       loginStep2: false, // 2차 인증
     };
@@ -132,7 +119,7 @@
   },
 
   methods: {
-    // ========== 초기화 및 유틸리티 ==========
+    // ========== 초기화 ==========
     checkAdminPage() {
       const redirect = this.restoreRedirect("redirect");
       this.isAdminPage = redirect && redirect.includes("/adm/");
@@ -152,28 +139,13 @@
         const res = await loginProc(this.member);
         if (res.status !== 200) return;
 
-        // 2차 인증에 필요한 이메일 정보가 있는지 확인
         if (res.data.email) {
-          this.memberInfo = res.data; // 인증코드 전송을 위한 이메일 정보 저장
-          // 없을 경우 2차 인증 패스
+          this.memberInfo = res.data;
         } else {
-          this.loginStep2 = true; // 2차 인증 패스
-          await this.loginSuccessProc(res); // 로그인 성공 처리
+          this.loginStep2 = true;
+          await this.loginSuccessProc(res);
         }
-        this.loginStep1 = true; // 1차 인증 성공
-
-        // const loginType = res.headers['login-type'];
-
-        // if (loginType === 'J') {
-        //   this.handleJWTLogin(res);
-        // } else if (loginType === 'S') {
-        //   this.handleSessionLogin(res);
-        // } else {
-        //   alert("알 수 없는 로그인 방식입니다.");
-        //   return;
-        // }
-
-        // await this.handleLoginSuccess();
+        this.loginStep1 = true;
 
       } catch (error) {
         alert(error.response?.data?.message || "로그인에 실패했습니다.");
@@ -182,53 +154,8 @@
       }
     },
 
-    // 인증코드 입력
-    inputCode(event) {
-      const input = event.target.value.replace(/[^0-9]/g, '');
-      this.memberInfo.code = input;
-    },
-
-    // 인증코드 확인
-    async fnCheck() {
-      try {
-          const res = await check2ndAuthProc(this.memberInfo);
-          if (res.status == 200) {
-              await this.loginSuccessProc(res); // 로그인 성공 처리
-          }
-      } catch (error) {
-          const errorData = error.response.data;
-          if (errorData.message != null && errorData.message != "") {
-              alert(error.response.data.message);
-              this.$refs.code.focus();
-          } else {
-              alert("에러가 발생했습니다.\n관리자에게 문의해주세요.");
-          }
-      }
-    },
-
-    // 인증코드 재전송
-    async fnResend() {
-      this.isLoading = true;
-      try {
-          const res = await sendAuthEmailProc(this.memberInfo);
-          if (res.status == 200) {
-              alert(res.data.message);
-          }
-      } catch (error) {
-          const errorData = error.response.data;
-          if (errorData.message != null && errorData.message != "") {
-              alert(error.response.data.message);
-          } else {
-              alert("에러가 발생했습니다.\n관리자에게 문의해주세요.");
-          }
-      } finally {
-          this.isLoading = false;
-      }
-    },
-
-    // 로그인 성공 시
     async loginSuccessProc(res) {
-      const loginType = res.headers['login-type']; // 세션/토큰 로그인 구분
+      const loginType = res.headers['login-type'];
       if (loginType === 'J') {
         this.handleJWTLogin(res);
       } else if (loginType === 'S') {
@@ -239,17 +166,16 @@
       }
       await this.handleLoginSuccess();
     },
+
     handleJWTLogin(res) {
       const token = res.headers.authorization;
       const userInfo = this.parseJWT(token);
-
       this.setAuthInfo("J", token, userInfo);
     },
 
     handleSessionLogin(res) {
       const userInfo = res.data;
       const roles = userInfo.roles.map(r => ({ authority: r.authrtCd }));
-
       this.setAuthInfo("S", null, { ...userInfo, roles });
     },
 
@@ -264,24 +190,45 @@
       return JSON.parse(jsonPayload);
     },
 
-    setAuthInfo(loginMode, token, userInfo) {
-      // Vuex 상태 저장
-      store.commit("setLoginMode", loginMode);
-      store.commit("setAuthorization", token);
-      store.commit("setMbrId", userInfo.mbrId);
-      store.commit("setMbrNm", userInfo.mbrNm);
-      store.commit("setRoles", userInfo.roles);
+    // ========== 2차 인증 ==========
+    inputCode(event) {
+      const input = event.target.value.replace(/[^0-9]/g, '');
+      this.memberInfo.code = input;
+    },
 
-      // localStorage 저장
-      localStorage.setItem("loginMode", loginMode);
-      localStorage.setItem("mbrId", userInfo.mbrId);
-      localStorage.setItem("mbrNm", userInfo.mbrNm);
-      localStorage.setItem("roles", JSON.stringify(userInfo.roles));
+    async fnCheck() {
+      try {
+        const res = await check2ndAuthProc(this.memberInfo);
+        if (res.status == 200) {
+          await this.loginSuccessProc(res);
+        }
+      } catch (error) {
+        const errorData = error.response.data;
+        if (errorData.message != null && errorData.message != "") {
+          alert(error.response.data.message);
+          this.$refs.code.focus();
+        } else {
+          alert("에러가 발생했습니다.\n관리자에게 문의해주세요.");
+        }
+      }
+    },
 
-      if (token) {
-        localStorage.setItem("authorization", token);
-      } else {
-        localStorage.removeItem("authorization");
+    async fnResend() {
+      this.isLoading = true;
+      try {
+        const res = await sendAuthEmailProc(this.memberInfo);
+        if (res.status == 200) {
+          alert(res.data.message);
+        }
+      } catch (error) {
+        const errorData = error.response.data;
+        if (errorData.message != null && errorData.message != "") {
+          alert(error.response.data.message);
+        } else {
+          alert("에러가 발생했습니다.\n관리자에게 문의해주세요.");
+        }
+      } finally {
+        this.isLoading = false;
       }
     },
 
@@ -303,15 +250,16 @@
       if (oauthSuccess !== 'true' && oauthSuccess !== true) return;
 
       try {
-        store.commit("setLoginMode", loginMode || "J");
-        localStorage.setItem("loginMode", loginMode || "J");
+        // 기존 시스템 로그인 모드 따라가기
+        const finalLoginMode = loginMode || store.state.loginMode || localStorage.getItem('loginMode') || 'J';
+        
+        store.commit("setLoginMode", finalLoginMode);
+        localStorage.setItem("loginMode", finalLoginMode);
 
-        if (loginMode === 'J') {
+        if (finalLoginMode === 'J') {
           await this.handleOAuthJWT();
-        } else if (loginMode === 'S') {
-          await this.handleOAuthSession();
         } else {
-          throw new Error('알 수 없는 로그인 모드: ' + loginMode);
+          await this.handleOAuthSession();
         }
 
         this.cleanupOAuth();
@@ -319,6 +267,7 @@
         await this.handleLoginSuccess();
 
       } catch (error) {
+        console.error("OAuth2 처리 실패:", error);
         this.handleOAuthError('processing_error', error.message);
       }
     },
@@ -336,70 +285,113 @@
     },
 
     async handleOAuthJWT() {
-      const oauthToken = this.getCookie('oauth_access_token');
-      if (!oauthToken) {
-        throw new Error('OAuth 토큰을 찾을 수 없습니다.');
-      }
-
-      const fullToken = oauthToken.startsWith('Bearer ') ? oauthToken : `Bearer ${oauthToken}`;
-      store.commit("setAuthorization", fullToken);
-      localStorage.setItem("authorization", fullToken);
-      this.deleteCookie('oauth_access_token');
-
+      console.log("JWT 모드 OAuth2 처리 시작");
+      
       try {
-        const userInfo = this.parseJWT(fullToken.replace('Bearer ', ''));
-        this.setAuthInfo("J", fullToken, userInfo);
-      } catch (jwtError) {
-        await this.fetchUserInfoFromServer();
+        const token = localStorage.getItem('authorization') 
+            || this.getCookie('refresh') 
+            || this.getCookie('Authorization');
+            
+        const headers = { 'Content-Type': 'application/json' };
+        
+        if (token) {
+          headers['Authorization'] = token.startsWith('Bearer ') ? token : `Bearer ${token}`;
+        }
+        
+        const response = await fetch('/oauth2/getUserInfo.json', {
+          method: 'POST',
+          headers: headers,
+          credentials: 'include'
+        });
+        
+        if (response.status === 200) {
+          const result = await response.json();
+          
+          if (result.success || result.data) {
+            const userInfo = result.data;
+            const roles = Array.isArray(userInfo.roles) ?
+                userInfo.roles.map(r => ({ authority: r.authrtCd || r.authority })) :
+                userInfo.roles;
+
+            this.setAuthInfo("J", token, { ...userInfo, roles });
+            
+          } else {
+            throw new Error('서버에서 실패 응답');
+          }
+        } else {
+          throw new Error('API 호출 실패: ' + response.status);
+        }
+        
+      } catch (error) {
+        throw error;
       }
     },
 
     async handleOAuthSession() {
-      store.commit("setAuthorization", null);
-      localStorage.removeItem("authorization");
-      this.deleteCookie('oauth_access_token');
+      try {
+        const userInfoRes = await getUserInfo();
+        if (!userInfoRes || userInfoRes.status !== 200) {
+          throw new Error('세션 정보를 가져올 수 없습니다.');
+        }
 
-      await this.fetchUserInfoFromServer();
+        const userInfo = userInfoRes.data.data;
+        const roles = Array.isArray(userInfo.roles) ?
+          userInfo.roles.map(r => ({ authority: r.authrtCd || r.authority })) :
+          userInfo.roles;
+
+        this.setAuthInfo('S', null, { ...userInfo, roles });
+        
+      } catch (error) {
+        console.error("세션 모드 처리 실패:", error);
+        throw error;
+      }
     },
 
-    async fetchUserInfoFromServer() {
-      const userInfoRes = await getUserInfo();
-      if (!userInfoRes || userInfoRes.status !== 200) {
-        throw new Error('사용자 정보를 가져올 수 없습니다.');
+    // ========== 공통 처리 ==========
+    setAuthInfo(loginMode, token, userInfo) {
+      // Store 설정
+      try {
+        if (typeof store !== 'undefined' && store.commit) {
+          store.commit("setLoginMode", loginMode);
+          store.commit("setAuthorization", token);
+          store.commit("setMbrId", userInfo.mbrId);
+          store.commit("setMbrNm", userInfo.mbrNm);
+          store.commit("setRoles", userInfo.roles);
+        }
+      } catch (e) {
+        console.warn("store 설정 실패, localStorage만 사용:", e);
       }
 
-      const userInfo = userInfoRes.data.data;
-      const roles = Array.isArray(userInfo.roles) ?
-        userInfo.roles.map(r => ({ authority: r.authrtCd || r.authority })) :
-        userInfo.roles;
+      // localStorage 저장
+      localStorage.setItem("loginMode", loginMode);
+      localStorage.setItem("mbrId", userInfo.mbrId);
+      localStorage.setItem("mbrNm", userInfo.mbrNm);
+      localStorage.setItem("roles", JSON.stringify(userInfo.roles));
 
-      const loginMode = localStorage.getItem("loginMode");
-      const token = userInfo.token || localStorage.getItem("authorization");
-
-      this.setAuthInfo(loginMode, token, { ...userInfo, roles });
+      if (token && loginMode === 'J') {
+        localStorage.setItem("authorization", token);
+      } else {
+        localStorage.removeItem("authorization");
+      }
     },
 
-    // ========== 로그인 성공 후 처리 ==========
     async handleLoginSuccess() {
       const isAdmin = store.state.roles.some(role => role.authority === "ROLE_ADMIN");
       let redirectUrl = this.restoreRedirect("redirect") || sessionStorage.getItem('oauth_redirect');
 
-      // 리다이렉트 URL 정리
       if (redirectUrl && this.shouldRedirectToMain(redirectUrl)) {
         redirectUrl = this.$filters.ctxPath("/");
       }
 
-      // Context Path 처리
       if (redirectUrl && !redirectUrl.startsWith(store.state.contextPath) && store.state.contextPath) {
         redirectUrl = this.$filters.ctxPath(redirectUrl);
       }
 
-      // 라우터 존재 여부 확인 후 이동
       const targetPath = this.getValidRedirectPath(redirectUrl, isAdmin);
+      
       await this.$nextTick();
       this.$router.push({ path: targetPath });
 
-      // 세션 정리
       sessionStorage.removeItem("redirect");
       sessionStorage.removeItem("oauth_redirect");
     },
@@ -442,15 +434,11 @@
       window.history.replaceState({}, document.title, cleanUrl);
     },
 
-    // ========== 유틸리티 메서드 ==========
+    // ========== 유틸리티 ==========
     getCookie(name) {
       const value = `; ${document.cookie}`;
       const parts = value.split(`; ${name}=`);
       return parts.length === 2 ? parts.pop().split(';').shift() : null;
-    },
-
-    deleteCookie(name) {
-      document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
     },
 
     moveSearchId() {
@@ -472,8 +460,8 @@
 
 <style scoped>
 .loading-blur {
-    pointer-events: none;
-    opacity: 0.4;
-    filter: grayscale(20%) blur(1px);
+  pointer-events: none;
+  opacity: 0.4;
+  filter: grayscale(20%) blur(1px);
 }
 </style>
(파일 끝에 줄바꿈 문자 없음)
Add a comment
List