hmkim 05-26
250526 김혜민 oauth2 추가
@bdf2e3f3825ce9ec5fe3250c8f72bba494b5b9e0
client/resources/api/login.js
--- client/resources/api/login.js
+++ client/resources/api/login.js
@@ -2,4 +2,19 @@
 
 export const loginProc = mber => {
     return apiClient.post(`/mbr/loginProc.json`, mber);
-}
(파일 끝에 줄바꿈 문자 없음)
+}
+
+export const oauthLogin = (provider) => {
+  const { API_SERVER_HOST } = require("../../../Global");
+  const oauthUrl = `//${API_SERVER_HOST}/oauth2/login?provider=${provider}`;
+  window.location.href = oauthUrl;
+};
+
+export const getOAuthUrl = (provider) => {
+  const { API_SERVER_HOST } = require("../../../Global");
+  return `//${API_SERVER_HOST}/oauth2/login?provider=${provider}`;
+};
+
+export const getOAuthUserInfo = () => {
+  return apiClient.get(`/oauth2/user-info`);
+};
(파일 끝에 줄바꿈 문자 없음)
client/views/pages/AppStore.js
--- client/views/pages/AppStore.js
+++ client/views/pages/AppStore.js
@@ -93,31 +93,71 @@
       try {
         const ctx = this.state.contextPath; // 캐시 초기화 전 contextPath 저장
         const admPath = this.state.path?.includes("/adm") // 캐시 초기화 전 경로 구분 (true: 관리자 페이지, false: 사용자 페이지)
-        const res = await logOutProc();
-        alert(res.data.message);
-        if (res.status == 200) {
-           // 1. 상태 초기화
-          commit("setStoreReset");
-          // 2. 로컬스토리지와 세션스토리지 초기화
-          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=/;";
-          // 4. 로그인 페이지로 이동
-          if(admPath) {
-            window.location = ctx + "/cmslogin.page";
-          } else {
-            window.location = ctx + "/login.page";
+        const loginMode = this.state.loginMode; // 로그인 모드 확인
+        
+        console.log("로그아웃 처리 시작 - 로그인 모드:", loginMode);
+        
+        // 로그인 모드에 따른 처리
+        if (loginMode === 'J') {
+          // JWT 방식인 경우만 서버 API 호출
+          try {
+            const res = await logOutProc();
+            alert(res.data.message);
+          } catch (error) {
+            console.log("JWT 로그아웃 API 에러, 클라이언트만 정리:", error);
+            // API 에러가 발생해도 클라이언트는 정리
           }
-        }
-      } catch(error) {
-        const errorData = error.response.data;
-        if (errorData.message != null && errorData.message != "") {
-          alert(error.response.data.message);
         } else {
-          alert("에러가 발생했습니다.\n관리자에게 문의해주세요.");
+          // 세션 방식 (OAuth 포함)은 서버 API 호출 없이 클라이언트만 정리
+          console.log("세션 방식 로그아웃 - 클라이언트만 정리");
+        }
+        
+        // 공통 클라이언트 정리 작업
+        // 1. 상태 초기화
+        commit("setStoreReset");
+        
+        // 2. 로컬스토리지와 세션스토리지 초기화
+        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=/;";
+        
+        console.log("로그아웃 완료");
+        
+        // 4. 로그인 페이지로 이동
+        if(admPath) {
+          window.location = ctx + "/cmslogin.page";
+        } else {
+          window.location = ctx + "/login.page";
+        }
+        
+      } catch(error) {
+        console.error("로그아웃 처리 중 오류:", error);
+        
+        // 에러가 발생해도 클라이언트 상태는 정리
+        commit("setStoreReset");
+        localStorage.clear();
+        sessionStorage.clear();
+        
+        const ctx = this.state.contextPath;
+        const admPath = this.state.path?.includes("/adm");
+        
+        // 에러 메시지 표시
+        const errorData = error.response?.data;
+        if (errorData?.message) {
+          alert(errorData.message);
+        } else {
+         
+        }
+        
+        // 로그인 페이지로 이동
+        if(admPath) {
+          window.location = ctx + "/cmslogin.page";
+        } else {
+          window.location = ctx + "/login.page";
         }
       }
     },
@@ -137,4 +177,4 @@
       commit("setStoreReset");
     },
   },
-});
+});
(파일 끝에 줄바꿈 문자 없음)
client/views/pages/login/Login.vue
--- client/views/pages/login/Login.vue
+++ client/views/pages/login/Login.vue
@@ -64,16 +64,38 @@
                 <p class="pl10 pr10 cursor" @click="moveSearchId">아이디찾기</p>
                 <p class="pl10 pr0 cursor" @click="moveResetPswd">비밀번호 초기화</p>
               </div>
-            </div>
+              <!-- <div class="oauth-login-section" v-if="!isAdminPage">
+                <div class="oauth-divider">
+                  <span>또는</span>
+                </div>
+                
+                <div class="oauth-buttons">
+                  <button class="oauth-btn kakao-btn" @click="fnOAuthLogin('kakao')">
+                    <img src="/images/kakao-icon.png" alt="카카오" class="oauth-icon" />
+                    카카오로 로그인
+                  </button>
+                  
+                  <button class="oauth-btn naver-btn" @click="fnOAuthLogin('naver')">
+                    <img src="/images/naver-icon.png" alt="네이버" class="oauth-icon" />
+                    네이버로 로그인
+                  </button>
+                  
+                  <button class="oauth-btn google-btn" @click="fnOAuthLogin('google')">
+                    <img src="/images/google-icon.png" alt="구글" class="oauth-icon" />
+                    구글로 로그인
+                  </button>
+                </div>
+            </div> -->
         </div>
       </div>
+    </div>
   </div>
 </template>
 
 <script>
 import { useStore } from "vuex";
 import store from "../AppStore";
-import { loginProc } from "../../../resources/api/login";
+import { loginProc, oauthLogin, getOAuthUserInfo } from "../../../resources/api/login";
 import queryParams from "../../../resources/js/queryParams";
 
 export default {
@@ -99,74 +121,114 @@
         this.isAdminPage = false;
       }
     },
+     
+   // OAuth2 로그인 처리 (API 모듈 사용)
+    fnOAuthLogin(provider) {
+    const redirectUrl = this.restoreRedirect("redirect") || this.$route.fullPath;
+    sessionStorage.setItem('oauth_redirect', redirectUrl);
+    
+    oauthLogin(provider);
+  },
     async fnLogin() {
       try {
         const res = await loginProc(this.member);
         if (res.status == 200) {
           const loginType = res.headers['login-type']; // 세션/토큰 로그인 구분
+          
           if (loginType === 'J') {
-              // JWT 방식
-              store.commit("setAuthorization", res.headers.authorization);
-              store.commit("setLoginMode", "J");
-              localStorage.setItem("loginMode", "J");
-              const base64String = 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);
-              store.commit("setMbrId", mbr.mbrId);
-              store.commit("setMbrNm", mbr.mbrNm);
-              store.commit("setRoles", mbr.roles);
-          } else if (loginType === 'S') {
-              store.commit("setLoginMode", "S");
-              localStorage.setItem("loginMode", "S");
-              const mbr = res.data;
-              store.commit("setAuthorization", null);
-              store.commit("setMbrId", mbr.mbrId);
-              store.commit("setMbrNm", mbr.mbrNm);
-              const roles = mbr.roles.map(r => ({ authority: r.authrtCd }));
-              store.commit("setRoles", roles);
-          } else {
-              alert("알 수 없는 로그인 방식입니다.");
-              return;
-          }
-          const isAdmin = store.state.roles.some(role => role.authority === "ROLE_ADMIN");
-          let url = this.restoreRedirect("redirect");
-          if (url != null && url != "") {
-            const ctx = store.state.contextPath;
-            if (ctx !== "") {
-              // redirect 값에서 Context Path 추가
-              url = this.$filters.ctxPath(url);
-            } else {
-              // redirect 값에서 기존 Context Path 제거
-              url = url.replace(/^\/[^\/]+/, ""); // 첫 번째 '/' 이후의 경로만 남김
-            }
-            const routeExists = this.$router.getRoutes().some(route => route.path === url);
+            // JWT 방식
+            const token = res.headers.authorization;
+            store.commit("setAuthorization", token);
+            store.commit("setLoginMode", "J");
             
-            if (url == this.$filters.ctxPath("/searchId.page") || url == this.$filters.ctxPath("/resetPswd.page")) {
-                this.$router.push({ path: this.$filters.ctxPath("/main.page") });
-              } else if (routeExists) {
-                this.$router.push({ path: url });
-              } else {
-                this.$router.push({
-                  path: isAdmin ? this.$filters.ctxPath("/adm/main.page") : this.$filters.ctxPath("/")
-                });
-              }
+            // localStorage에도 저장
+            localStorage.setItem("authorization", token);
+            localStorage.setItem("loginMode", "J");
+            
+            const base64String = token.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);
+            
+            store.commit("setMbrId", mbr.mbrId);
+            store.commit("setMbrNm", mbr.mbrNm);
+            store.commit("setRoles", mbr.roles);
+            
+            // localStorage에 사용자 정보도 저장
+            localStorage.setItem("mbrId", mbr.mbrId);
+            localStorage.setItem("mbrNm", mbr.mbrNm);
+            localStorage.setItem("roles", JSON.stringify(mbr.roles));
+            
+          } else if (loginType === 'S') {
+            store.commit("setLoginMode", "S");
+            localStorage.setItem("loginMode", "S");
+            const mbr = res.data;
+            store.commit("setAuthorization", null);
+            store.commit("setMbrId", mbr.mbrId);
+            store.commit("setMbrNm", mbr.mbrNm);
+            const roles = mbr.roles.map(r => ({ authority: r.authrtCd }));
+            store.commit("setRoles", roles);
+            
+            // localStorage에 세션 정보도 저장
+            localStorage.setItem("mbrId", mbr.mbrId);
+            localStorage.setItem("mbrNm", mbr.mbrNm);
+            localStorage.setItem("roles", JSON.stringify(roles));
+            
           } else {
-            this.$router.push({
-                path: isAdmin ? this.$filters.ctxPath("/adm/main.page") : this.$filters.ctxPath("/")
-              });
+            alert("알 수 없는 로그인 방식입니다.");
+            return;
           }
 
-          
+          // 페이지 이동 처리
+          await this.handleLoginSuccess();
         }
       } catch (error) {
-        alert(error.response.data.message);
+        console.error("로그인 에러:", error);
+        alert(error.response?.data?.message || "로그인에 실패했습니다.");
       }
     },
+
+    // 로그인 성공 후 페이지 이동 처리
+    async handleLoginSuccess() {
+      const isAdmin = store.state.roles.some(role => role.authority === "ROLE_ADMIN");
+      let redirectUrl = this.restoreRedirect("redirect");
+      
+      if (redirectUrl && redirectUrl !== "") {
+        // 특정 페이지들은 메인으로 리다이렉트
+        if (redirectUrl.includes("/searchId.page") || redirectUrl.includes("/resetPswd.page") || redirectUrl.includes("/login.page")) {
+          redirectUrl = this.$filters.ctxPath("/");
+        }
+        
+        // Context Path 처리
+        if (!redirectUrl.startsWith(store.state.contextPath) && store.state.contextPath) {
+          redirectUrl = this.$filters.ctxPath(redirectUrl);
+        }
+        
+        
+        // 라우터에 해당 경로가 존재하는지 확인
+        const routeExists = this.$router.getRoutes().some(route => route.path === redirectUrl);
+        
+        if (routeExists) {
+          await this.$nextTick();
+          this.$router.push({ path: redirectUrl });
+        } else {
+          const defaultPath = isAdmin ? this.$filters.ctxPath("/adm/main.page") : this.$filters.ctxPath("/");
+          this.$router.push({ path: defaultPath });
+        }
+      } else {
+        const defaultPath = isAdmin ? this.$filters.ctxPath("/adm/main.page") : this.$filters.ctxPath("/");
+        await this.$nextTick();
+        this.$router.push({ path: defaultPath });
+      }
+      
+      // redirect 세션 정리
+      sessionStorage.removeItem("redirect");
+    },
+
     moveSearchId() {
       this.$router.push({
         path: this.$filters.ctxPath("/resetPswd.page"),
@@ -183,13 +245,86 @@
         },
       });
     },
+    
+ // OAuth2 로그인 성공 후 처리
+async handleOAuthCallback() {
+  const urlParams = new URLSearchParams(window.location.search);
+  const error = urlParams.get('error');
+  
+  if (error) {
+    alert('소셜 로그인에 실패했습니다: ' + decodeURIComponent(urlParams.get('message') || error));
+    return;
+  }
+  
+  // URL에 OAuth 관련 파라미터가 있는 경우
+  if (this.$route.query.oauth_success) {
+    try {
+      console.log("OAuth2 로그인 성공! 간단한 세션 처리를 시작합니다.");
+      
+      // OAuth는 일반적으로 세션 방식이므로 세션으로 설정
+      store.commit("setLoginMode", "S");
+      localStorage.setItem("loginMode", "S");
+      
+      // 기본 사용자 정보 설정 - 서버에서 세션에 저장된 정보 사용
+      // 실제 사용자 정보는 다른 API나 페이지 로드 시 가져올 수 있음
+      const defaultMbrId = 'oauth_user_' + Date.now(); // 임시 ID
+      const defaultMbrNm = 'OAuth 사용자';
+      const defaultRoles = [{ authority: 'ROLE_USER' }];
+      
+      store.commit("setAuthorization", null);
+      store.commit("setMbrId", defaultMbrId);
+      store.commit("setMbrNm", defaultMbrNm);
+      store.commit("setRoles", defaultRoles);
+      
+      // localStorage에 정보 저장
+      localStorage.setItem("mbrId", defaultMbrId);
+      localStorage.setItem("mbrNm", defaultMbrNm);
+      localStorage.setItem("roles", JSON.stringify(defaultRoles));
+      
+      console.log("OAuth 세션 로그인 완료 - Store 상태:", {
+        authorization: store.state.authorization,
+        mbrId: store.state.mbrId,
+        mbrNm: store.state.mbrNm,
+        roles: store.state.roles,
+        loginMode: store.state.loginMode
+      });
+
+      // 페이지 이동 처리
+      await this.$nextTick();
+      
+      // 리다이렉트 URL 처리
+      const redirectUrl = sessionStorage.getItem('oauth_redirect');
+      sessionStorage.removeItem('oauth_redirect');
+      
+      if (redirectUrl && redirectUrl !== '/login' && !redirectUrl.includes('/login.page')) {
+        console.log("OAuth 리다이렉트:", redirectUrl);
+        this.$router.replace({ path: redirectUrl });
+      } else {
+        // 메인 페이지로 이동
+        const mainPath = this.$filters.ctxPath("/");
+        console.log("OAuth 메인 페이지로 이동:", mainPath);
+        this.$router.replace({ path: mainPath });
+      }
+      
+    } catch (error) {
+      console.error('OAuth 콜백 처리 실패:', error);
+      alert('OAuth 로그인 처리 중 오류가 발생했습니다: ' + (error.message || '알 수 없는 오류'));
+      
+      // 에러 발생 시 로그인 페이지로 이동
+      store.commit("setStoreReset");
+      this.$router.push({ path: this.$filters.ctxPath("/login.page") });
+    }
+  }
+}
   },
   watch: {},
   computed: {},
   components: {},
   created() {
     this.checkAdminPage();
+    // OAuth2 콜백 처리
+    this.handleOAuthCallback();
   },
   mounted() {},
 };
-</script>
+</script>
(파일 끝에 줄바꿈 문자 없음)
Add a comment
List