hmkim 05-23
250523 김혜민 oauth2 적용중
@c799e2456dedb220383007a572da4d20c2174141
build.gradle
--- build.gradle
+++ build.gradle
@@ -47,6 +47,7 @@
     implementation 'org.springframework.boot:spring-boot-starter-data-redis'
     implementation 'org.springframework.boot:spring-boot-starter-cache'
 
+
     implementation ('org.egovframe.rte:org.egovframe.rte.ptl.mvc:4.2.0'){
         exclude group: 'commons-logging', module: 'commons-logging'
     }
@@ -83,6 +84,8 @@
     implementation 'org.jsoup:jsoup:1.19.1'
     // Gmail SMTP
     implementation 'org.springframework.boot:spring-boot-starter-mail'
+    //oauth2
+    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
 
     testImplementation 'org.springframework.boot:spring-boot-starter-test'
     testImplementation 'org.springframework.security:spring-security-test'
src/main/java/com/takensoft/cms/mber/dao/MberDAO.java
--- src/main/java/com/takensoft/cms/mber/dao/MberDAO.java
+++ src/main/java/com/takensoft/cms/mber/dao/MberDAO.java
@@ -66,4 +66,53 @@
      * 사용자 정보 조회
      */
     MberVO findByMber(String mbrId);
+
+    /**
+     * @param email - 이메일
+     * @return MberVO - 사용자 정보
+     *
+     * 이메일로만 사용자 조회 (provider 무관)
+     */
+    MberVO findByEmail(String email);
+
+    /**
+     * @param email - 이메일
+     * @param provider - OAuth2 제공자
+     * @return MberVO - OAuth2 사용자 정보
+     *
+     * 이메일과 제공자로 사용자 조회
+     */
+    MberVO findByEmailAndProvider(String email, String mbrType);
+
+    /**
+     * @param mberVO - OAuth2 사용자 정보
+     * @return int - OAuth2 사용자 저장 결과
+     *
+     * OAuth2 사용자 저장
+     */
+    int saveOAuthUser(MberVO mberVO);
+
+    /**
+     * @param mberVO - OAuth2 사용자 정보
+     * @return int - OAuth2 사용자 업데이트 결과
+     *
+     * OAuth2 사용자 정보 업데이트
+     */
+    int updateOAuthUser(MberVO mberVO);
+
+    /**
+     * @param mberVO - OAuth2 연동 정보
+     * @return int - 연동 결과
+     *
+     * 기존 계정에 OAuth2 정보 연동
+     */
+    int linkOAuth2Account(MberVO mberVO);
+
+    /**
+     * @param mbrId - 회원 ID
+     * @return List<MberAuthorVO> - 회원 권한 목록
+     *
+     * 회원 ID로 권한 목록 조회
+     */
+    List<MberAuthorVO> findAuthoritiesByMbrId(String mbrId);
 }
(파일 끝에 줄바꿈 문자 없음)
src/main/java/com/takensoft/cms/mber/service/Impl/AdmMbrServiceImpl.java
--- src/main/java/com/takensoft/cms/mber/service/Impl/AdmMbrServiceImpl.java
+++ src/main/java/com/takensoft/cms/mber/service/Impl/AdmMbrServiceImpl.java
@@ -98,7 +98,8 @@
     @Override
     public AdmMbrDTO mbrDetail(String mbrId){
         try {
-        AdmMbrDTO admMbrDTO = new AdmMbrDTO();
+            verificationService.verifyAccess(mbrId); // 접근 검증
+            AdmMbrDTO admMbrDTO = new AdmMbrDTO();
 
         // mbrId가 있는 경우
         if (mbrId != null) {
src/main/java/com/takensoft/cms/mber/service/Impl/MberServiceImpl.java
--- src/main/java/com/takensoft/cms/mber/service/Impl/MberServiceImpl.java
+++ src/main/java/com/takensoft/cms/mber/service/Impl/MberServiceImpl.java
@@ -24,6 +24,8 @@
 import jakarta.servlet.http.HttpServletRequest;
 
 import java.util.HashMap;
+import java.util.List;
+
 /**
  * @author takensoft
  * @since 2024.04.01
@@ -265,4 +267,165 @@
             throw e;
         }
     }
+
+
+
+    // OAuth2 관련 메서드 구현
+
+    /**
+     * @param email - 이메일
+     * @return MberVO - 사용자 정보
+     * @throws DataAccessException - db 관련 예외 발생 시
+     * @throws Exception - 그 외 예외 발생 시
+     *
+     * 이메일로만 사용자 조회 (provider 무관)
+     */
+    @Override
+    @Transactional(readOnly = true)
+    public MberVO findByEmail(String email) {
+        try {
+            return mberDAO.findByEmail(email);
+        } catch (DataAccessException dae) {
+            throw dae;
+        } catch (Exception e) {
+            throw e;
+        }
+    }
+
+    /**
+     * @param email - 이메일
+     * @param mbrType - 회원 유형 (K, N, G, F, S)
+     * @return MberVO - 사용자 정보
+     * @throws DataAccessException - db 관련 예외 발생 시
+     * @throws Exception - 그 외 예외 발생 시
+     *
+     * 이메일과 회원 유형으로 사용자 조회
+     */
+    @Override
+    @Transactional(readOnly = true)
+    public MberVO findByEmailAndProvider(String email, String mbrType) {
+        try {
+            MberVO user = mberDAO.findByEmailAndProvider(email, mbrType); // 파라미터 직접 전달
+
+            // 권한 정보도 함께 조회
+            if (user != null) {
+                List<MberAuthorVO> authorities = mberDAO.findAuthoritiesByMbrId(user.getMbrId());
+                user.setAuthorList(authorities);
+            }
+            return user;
+        } catch (DataAccessException dae) {
+            throw dae;
+        } catch (Exception e) {
+            throw e;
+        }
+    }
+
+    /**
+     * @param user - OAuth2 사용자 정보
+     * @return MberVO - 저장된 사용자 정보
+     * @throws CustomInsertFailException - 사용자 저장 실패 예외 발생 시
+     * @throws DataAccessException - db 관련 예외 발생 시
+     * @throws Exception - 그 외 예외 발생 시
+     *
+     * OAuth2 사용자 저장
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public MberVO saveOAuthUser(MberVO user) {
+        try {
+            // 회원 아이디 생성
+            String mbrId = mberIdgn.getNextStringId();
+            user.setMbrId(mbrId);
+
+            // 기본값 설정
+            user.setLgnId(user.getEml().toLowerCase());
+            user.setMbrStts("1"); // 승인 상태
+            user.setUseYn(true);
+            user.setSmsRcptnAgreYn("N");
+            user.setEmlRcptnAgreYn("N");
+            user.setPrvcRlsYn("N");
+            user.setSysPvsnYn("1"); // 사용자 데이터
+            user.setRgtr("OAUTH2_SYSTEM");
+
+            // OAuth2 사용자 저장
+            int result = mberDAO.saveOAuthUser(user);
+            if(result == 0) {
+                throw new CustomInsertFailException("OAuth2 사용자 저장에 실패했습니다.");
+            }
+
+            // 권한 저장
+            if(user.getAuthorList() != null && !user.getAuthorList().isEmpty()) {
+                for(MberAuthorVO authority : user.getAuthorList()) {
+                    authority.setMbrId(mbrId);
+                    authority.setRgtr("OAUTH2_SYSTEM");
+                    int authorResult = mberDAO.authorSave(authority);
+                    if(authorResult == 0) {
+                        throw new CustomInsertFailException("OAuth2 사용자 권한 저장에 실패했습니다.");
+                    }
+                }
+            }
+
+            return user;
+        } catch (DataAccessException dae) {
+            throw dae;
+        } catch (Exception e) {
+            throw e;
+        }
+    }
+
+    /**
+     * @param user - OAuth2 사용자 정보
+     * @return MberVO - 업데이트된 사용자 정보
+     * @throws CustomUpdateFailException - 사용자 업데이트 실패 예외 발생 시
+     * @throws DataAccessException - db 관련 예외 발생 시
+     * @throws Exception - 그 외 예외 발생 시
+     *
+     * OAuth2 사용자 정보 업데이트
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public MberVO updateOAuthUser(MberVO user) {
+        try {
+            user.setMdfr("OAUTH2_SYSTEM");
+
+            int result = mberDAO.updateOAuthUser(user);
+            if(result == 0) {
+                throw new CustomUpdateFailException("OAuth2 사용자 정보 업데이트에 실패했습니다.");
+            }
+
+            return user;
+        } catch (DataAccessException dae) {
+            throw dae;
+        } catch (Exception e) {
+            throw e;
+        }
+    }
+
+    /**
+     * @param user - OAuth2 연동 정보가 추가된 사용자 정보
+     * @return MberVO - 연동된 사용자 정보
+     * @throws CustomUpdateFailException - 계정 연동 실패 예외 발생 시
+     * @throws DataAccessException - db 관련 예외 발생 시
+     * @throws Exception - 그 외 예외 발생 시
+     *
+     * 기존 계정에 OAuth2 정보 연동
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public MberVO linkOAuth2Account(MberVO user) {
+        try {
+            user.setMdfr("OAUTH2_LINK");
+
+            int result = mberDAO.linkOAuth2Account(user);
+            if(result == 0) {
+                throw new CustomUpdateFailException("OAuth2 계정 연동에 실패했습니다.");
+            }
+
+            return user;
+        } catch (DataAccessException dae) {
+            throw dae;
+        } catch (Exception e) {
+            throw e;
+        }
+    }
 }
(파일 끝에 줄바꿈 문자 없음)
src/main/java/com/takensoft/cms/mber/service/MberService.java
--- src/main/java/com/takensoft/cms/mber/service/MberService.java
+++ src/main/java/com/takensoft/cms/mber/service/MberService.java
@@ -62,4 +62,45 @@
      * 비밀번호 수정
      */
     public int updatePassword(PasswordDTO passwordDTO);
+
+    /**
+     * @param email - 이메일
+     * @return MberVO - 사용자 정보
+     *
+     * 이메일로만 사용자 조회
+     */
+    public MberVO findByEmail(String email);
+
+    /**
+     * @param email - 이메일
+     * @param mbrType - 회원 유형 (K, N, G, F, S)
+     * @return MberVO - 사용자 정보
+     *
+     * 이메일과 회원 유형으로 사용자 조회
+     */
+    public MberVO findByEmailAndProvider(String email, String mbrType);
+
+    /**
+     * @param user - OAuth2 사용자 정보
+     * @return MberVO - 저장된 사용자 정보
+     *
+     * OAuth2 사용자 저장
+     */
+    public MberVO saveOAuthUser(MberVO user);
+
+    /**
+     * @param user - OAuth2 사용자 정보
+     * @return MberVO - 업데이트된 사용자 정보
+     *
+     * OAuth2 사용자 정보 업데이트
+     */
+    public MberVO updateOAuthUser(MberVO user);
+
+    /**
+     * @param user - OAuth2 연동 정보가 추가된 사용자 정보
+     * @return MberVO - 연동된 사용자 정보
+     *
+     * 기존 계정에 OAuth2 정보 연동
+     */
+    public MberVO linkOAuth2Account(MberVO user);
 }
(파일 끝에 줄바꿈 문자 없음)
src/main/java/com/takensoft/common/config/SecurityConfig.java
--- src/main/java/com/takensoft/common/config/SecurityConfig.java
+++ src/main/java/com/takensoft/common/config/SecurityConfig.java
@@ -12,6 +12,10 @@
 import com.takensoft.common.exception.CustomAuthenticationEntryPoint;
 import com.takensoft.common.util.JWTUtil;
 import com.takensoft.common.util.SessionUtil;
+import com.takensoft.common.oauth.service.Impl.CustomOAuth2UserServiceImpl;
+import com.takensoft.common.oauth.handler.OAuth2AuthenticationSuccessHandler;
+import com.takensoft.common.oauth.handler.OAuth2AuthenticationFailureHandler;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -22,6 +26,7 @@
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.http.SessionCreationPolicy;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
 import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 import org.springframework.security.web.context.SecurityContextPersistenceFilter;
@@ -37,6 +42,7 @@
  * @modification
  *     since    |    author    | description
  *  2025.01.22  |  takensoft   | 최초 등록
+ *  2025.05.22  |  takensoft   | OAuth2 로그인 추가
  *
  * Spring Security 설정을 위한 Config
  */
@@ -59,6 +65,15 @@
     private final LoginPolicyService loginPolicyService;
     private final SessionUtil sessionUtil;
 
+    @Autowired
+    private CustomOAuth2UserServiceImpl customOAuth2UserServiceImpl;
+
+    @Autowired
+    private OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler;
+
+    @Autowired
+    private OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler;
+
     private static String FRONT_URL;    // 프론트 접근 허용 URL
     private static long JWT_ACCESSTIME; // access 토큰 유지 시간
     private static long JWT_REFRESHTIME; // refresh 토큰 유지 시간
@@ -67,16 +82,7 @@
     private final RedisTemplate<String, String> redisTemplate;
 
     /**
-     * @param authenticationConfiguration - 인증 구성 객체
-     * @param jwtUtil - JWT 유틸리티 객체
-     * @param authenticationEntryPoint - 인증 실패 시 처리 엔트리 포인트
-     * @param accessDenieHandler - 접근 거부 처리 핸들러
-     * @param fUrl - 프론트엔드 URL (application.yml에서 값을 읽어 옴)
-     * @param aTime - JWT 접근 토큰 유효 시간 (application.yml에서 값을 읽어 옴)
-     * @param rTime - JWT 리프레시 토큰 유효 시간 (application.yml에서 값을 읽어 옴)
-     * @param ctime - 쿠키 유효 시간 (application.yml에서 값을 읽어 옴)
-     * @param redisTemplate
-*
+     * SecurityConfig 생성자
      */
     public SecurityConfig(AuthenticationConfiguration authenticationConfiguration, JWTUtil jwtUtil, RefreshTokenService refreshTokenService, CntxtPthService cntxtPthService, AccesCtrlService accesCtrlService, AppConfig appConfig,
                           LgnHstryService lgnHstryService, CustomAuthenticationEntryPoint authenticationEntryPoint, CustomAccessDenieHandler accessDenieHandler, HttpRequestUtil httpRequestUtil,
@@ -140,7 +146,7 @@
                         CorsConfiguration configuration = new CorsConfiguration();
                         configuration.setAllowedOrigins(Collections.singletonList(FRONT_URL)); // 허용할 프론트 포트 포함 경로 입력
                         configuration.setAllowedMethods(Collections.singletonList("*")); // 허용할 메소드(GET, POST, PUT 등)
-                        configuration.setAllowedHeaders(Collections.singletonList("*")); // 허용할 헤드
+                        configuration.setAllowedHeaders(Collections.singletonList("*")); // 허용할 헤더
                         configuration.setAllowCredentials(true); // 프론트에서 credentials 설정하면 true
                         configuration.setMaxAge(3600L); // 허용을 물고 있을 시간
                         configuration.setExposedHeaders(Collections.singletonList("Authorization")); // 서버에서 JWT를 Authorization에 담아 보내기 위해 허용을 함
@@ -148,6 +154,7 @@
                     }
                 })
         );
+
         // csrf disable
         http.csrf((auth) -> auth.disable());
         // formLogin disable
@@ -163,10 +170,9 @@
         );
 
         http.authorizeHttpRequests((auth) -> auth
-                .requestMatchers("/", "/mbr/**", "/refresh/**", "/sys/**", "/editFileUpload/**", "/fileUpload/**").permitAll() // 회원, 토큰, 시스템 제공, 파일 접근 모두 허용
+                .requestMatchers("/", "/mbr/**", "/refresh/**", "/sys/**", "/editFileUpload/**", "/fileUpload/**", "/oauth2/**", "/login/oauth2/**").permitAll() // 회원, 토큰, 시스템 제공, 파일, OAuth2 접근 모두 허용
                 .requestMatchers("/admin/**").hasRole("ADMIN") // 관리자 페이지는 ADMIN 권한을 가진 사용자만 접근 가능
                 .anyRequest().authenticated() // 그 외에는 로그인한 사용자만 접근 가능
-//                .anyRequest().permitAll() // 모든 사용자 접근 가능
         );
 
         // Context Path 검증 필터
@@ -180,7 +186,16 @@
 
         // 로그인 필터
         http.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil, refreshTokenService, lgnHstryService, httpRequestUtil,
-                 loginModeService, loginPolicyService, sessionUtil, JWT_ACCESSTIME, JWT_REFRESHTIME, COOKIE_TIME, redisTemplate), UsernamePasswordAuthenticationFilter.class);
+                loginModeService, loginPolicyService, sessionUtil, JWT_ACCESSTIME, JWT_REFRESHTIME, COOKIE_TIME, redisTemplate), UsernamePasswordAuthenticationFilter.class);
+
+        // OAuth2 로그인 설정
+        http.oauth2Login(oauth2 -> oauth2
+                .userInfoEndpoint(userInfo -> userInfo
+                        .userService(customOAuth2UserServiceImpl)
+                )
+                .successHandler(oAuth2AuthenticationSuccessHandler)
+                .failureHandler(oAuth2AuthenticationFailureHandler)
+        );
 
         return http.build();
     }
 
src/main/java/com/takensoft/common/oauth/handler/OAuth2AuthenticationFailureHandler.java (added)
+++ src/main/java/com/takensoft/common/oauth/handler/OAuth2AuthenticationFailureHandler.java
@@ -0,0 +1,70 @@
+package com.takensoft.common.oauth.handler;
+
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * @author takensoft
+ * @since 2025.05.22
+ * @modification
+ *     since    |    author    | description
+ *  2025.05.22  |  takensoft   | 최초 등록
+ *
+ * OAuth2 로그인 실패 핸들러
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class OAuth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
+
+    @Value("${front.url}")
+    private String FRONT_URL;
+
+    @Override
+    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
+                                        AuthenticationException exception) throws IOException, ServletException {
+
+        log.error("OAuth2 로그인 실패: {}", exception.getMessage());
+
+        String errorMessage = mapErrorMessage(exception);
+        String encodedMessage = URLEncoder.encode(errorMessage, StandardCharsets.UTF_8);
+
+        String redirectUrl = String.format("%s/login?error=oauth2_failed&message=%s",
+                FRONT_URL, encodedMessage);
+
+        getRedirectStrategy().sendRedirect(request, response, redirectUrl);
+    }
+
+    /**
+     * 에러 메시지 매핑
+     */
+    private String mapErrorMessage(AuthenticationException exception) {
+        String message = exception.getMessage();
+
+        if (message.contains("access_denied")) {
+            return "사용자가 인증을 취소했습니다.";
+        }
+        if (message.contains("invalid_request")) {
+            return "잘못된 OAuth2 요청입니다.";
+        }
+        if (message.contains("unauthorized_client")) {
+            return "인증되지 않은 클라이언트입니다.";
+        }
+        if (message.contains("server_error")) {
+            return "OAuth2 서버 오류가 발생했습니다.";
+        }
+
+        return "OAuth2 로그인에 실패했습니다.";
+    }
+}(파일 끝에 줄바꿈 문자 없음)
 
src/main/java/com/takensoft/common/oauth/handler/OAuth2AuthenticationSuccessHandler.java (added)
+++ src/main/java/com/takensoft/common/oauth/handler/OAuth2AuthenticationSuccessHandler.java
@@ -0,0 +1,166 @@
+package com.takensoft.common.oauth.handler;
+
+import com.takensoft.cms.loginPolicy.service.LoginModeService;
+import com.takensoft.cms.loginPolicy.service.LoginPolicyService;
+import com.takensoft.cms.mber.service.LgnHstryService;
+import com.takensoft.cms.mber.vo.LgnHstryVO;
+import com.takensoft.common.oauth.vo.CustomOAuth2UserVO;
+import com.takensoft.common.util.HttpRequestUtil;
+import com.takensoft.common.util.JWTUtil;
+import com.takensoft.common.util.SessionUtil;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author takensoft
+ * @since 2025.05.22
+ * @modification
+ *     since    |    author    | description
+ *  2025.05.22  |  takensoft   | 최초 등록
+ *
+ * OAuth2 로그인 성공 핸들러
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
+
+    private final LgnHstryService lgnHstryService;
+    private final HttpRequestUtil httpRequestUtil;
+    private final LoginModeService loginModeService;
+    private final LoginPolicyService loginPolicyService;
+    private final SessionUtil sessionUtil;
+    private final JWTUtil jwtUtil;
+    private final RedisTemplate<String, String> redisTemplate;
+    private final ApplicationEventPublisher eventPublisher;
+
+    @Value("${jwt.accessTime}")
+    private long JWT_ACCESSTIME;
+
+    @Value("${front.url}")
+    private String FRONT_URL;
+
+    @Override
+    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException{
+
+        CustomOAuth2UserVO oAuth2User = (CustomOAuth2UserVO) authentication.getPrincipal();
+
+        try {
+            log.info("OAuth2 로그인 성공 - Provider: {}, Email: {}",
+                    oAuth2User.getProvider(), oAuth2User.getEmail());
+
+            // 1. 비동기로 사용자 정보 저장/업데이트 처리
+            eventPublisher.publishEvent(new OAuth2UserSaveEvent(oAuth2User));
+
+            // 2. 로그인 이력 저장
+            saveLoginHistory(request, oAuth2User);
+
+            // 3. 로그인 모드에 따른 토큰/세션 처리
+            String loginMode = loginModeService.getLoginMode();
+            String tempUserId = createTempUserId(oAuth2User);
+            processLoginByMode(request, response, oAuth2User, tempUserId, loginMode);
+
+            // 4. 프론트엔드 OAuth 콜백 페이지로 리다이렉트
+            String redirectUrl = FRONT_URL + "/oauth/callback";
+            getRedirectStrategy().sendRedirect(request, response, redirectUrl);
+
+        } catch (Exception e) {
+            log.error("OAuth2 로그인 처리 중 오류 발생", e);
+            String errorUrl = FRONT_URL + "/login?error=oauth2_processing_failed";
+            getRedirectStrategy().sendRedirect(request, response, errorUrl);
+        }
+    }
+
+    /**
+     * 로그인 이력 저장
+     */
+    private void saveLoginHistory(HttpServletRequest request, CustomOAuth2UserVO oAuth2User) {
+        try {
+            LgnHstryVO lgnHstryVO = new LgnHstryVO();
+            lgnHstryVO.setLgnId(oAuth2User.getEmail());
+            lgnHstryVO.setLgnType("1"); // 일반 사용자
+            lgnHstryVO.setCntnIp(httpRequestUtil.getIp(request));
+            lgnHstryVO.setCntnOperSys(httpRequestUtil.getOS(httpRequestUtil.getUserAgent(request)));
+            lgnHstryVO.setDeviceNm(httpRequestUtil.getDevice(httpRequestUtil.getUserAgent(request)));
+            lgnHstryVO.setBrwsrNm(httpRequestUtil.getBrowser(httpRequestUtil.getUserAgent(request)));
+
+            lgnHstryService.LgnHstrySave(lgnHstryVO);
+        } catch (Exception e) {
+            log.error("OAuth2 로그인 이력 저장 실패", e);
+        }
+    }
+
+    /**
+     * 임시 사용자 ID 생성
+     */
+    private String createTempUserId(CustomOAuth2UserVO oAuth2User) {
+        return oAuth2User.getProvider() + "_" + oAuth2User.getId();
+    }
+
+    /**
+     * 로그인 모드에 따른 처리
+     */
+    private void processLoginByMode(HttpServletRequest request, HttpServletResponse response, CustomOAuth2UserVO oAuth2User, String tempUserId, String loginMode){
+
+        // JWT 토큰 생성
+        String accessToken = jwtUtil.createJwt(
+                "Authorization",
+                tempUserId,
+                oAuth2User.getEmail(),
+                oAuth2User.getName(),
+                null, // roles는 DB 저장 후 갱신 예정
+                JWT_ACCESSTIME
+        );
+
+        if ("S".equals(loginMode)) {
+            // 세션 모드 처리
+            processSessionMode(request, tempUserId, accessToken, oAuth2User);
+        } else {
+            // JWT 모드 처리 (기본값)
+            processJwtMode(response, tempUserId, accessToken);
+        }
+
+        response.setHeader("login-type", loginMode);
+    }
+
+    /**
+     * 세션 모드 처리
+     */
+    private void processSessionMode(HttpServletRequest request, String tempUserId,String accessToken, CustomOAuth2UserVO oAuth2User) {
+        HttpSession session = request.getSession(true);
+        session.setAttribute("JWT_TOKEN", accessToken);
+        session.setAttribute("oauth2User", oAuth2User);
+
+        // 중복 로그인 정책 확인
+        if (!loginPolicyService.getPolicy()) {
+            sessionUtil.registerSession(tempUserId, session);
+        }
+    }
+
+    /**
+     * JWT 모드 처리
+     */
+    private void processJwtMode(HttpServletResponse response, String tempUserId, String accessToken) {
+        response.setHeader("Authorization", accessToken);
+
+        // 중복 로그인 정책 확인
+        if (!loginPolicyService.getPolicy()) {
+            redisTemplate.delete("jwt:" + tempUserId);
+            redisTemplate.opsForValue().set("jwt:" + tempUserId, accessToken,
+                    JWT_ACCESSTIME, TimeUnit.MILLISECONDS);
+        }
+    }
+}(파일 끝에 줄바꿈 문자 없음)
 
src/main/java/com/takensoft/common/oauth/handler/OAuth2UserSaveEvent.java (added)
+++ src/main/java/com/takensoft/common/oauth/handler/OAuth2UserSaveEvent.java
@@ -0,0 +1,25 @@
+package com.takensoft.common.oauth.handler;
+
+import com.takensoft.common.oauth.vo.CustomOAuth2UserVO;
+import lombok.Getter;
+import org.springframework.context.ApplicationEvent;
+
+/**
+ * @author takensoft
+ * @since 2025.05.22
+ * @modification
+ *     since    |    author    | description
+ *  2025.05.22  |  takensoft   | 최초 등록
+ *
+ * OAuth2 사용자 저장 이벤트
+ */
+@Getter
+public class OAuth2UserSaveEvent extends ApplicationEvent {
+
+    private final CustomOAuth2UserVO oAuth2User;
+
+    public OAuth2UserSaveEvent(CustomOAuth2UserVO oAuth2User) {
+        super(oAuth2User);
+        this.oAuth2User = oAuth2User;
+    }
+}(파일 끝에 줄바꿈 문자 없음)
 
src/main/java/com/takensoft/common/oauth/handler/OAuth2UserSaveEventListener.java (added)
+++ src/main/java/com/takensoft/common/oauth/handler/OAuth2UserSaveEventListener.java
@@ -0,0 +1,119 @@
+package com.takensoft.common.oauth.handler;
+
+import com.takensoft.cms.mber.service.MberService;
+import com.takensoft.cms.mber.vo.MberAuthorVO;
+import com.takensoft.cms.mber.vo.MberVO;
+import com.takensoft.common.oauth.vo.CustomOAuth2UserVO;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author takensoft
+ * @since 2025.05.22
+ * @modification
+ *     since    |    author    | description
+ *  2025.05.22  |  takensoft   | 최초 등록
+ *
+ * OAuth2 사용자 저장 이벤트 리스너
+ */
+@Component
+@RequiredArgsConstructor
+@Slf4j
+public class OAuth2UserSaveEventListener {
+
+    private final MberService mberService;
+
+    @EventListener
+    @Async
+    public void handleOAuth2UserSaveEvent(OAuth2UserSaveEvent event) {
+        try {
+            CustomOAuth2UserVO oAuth2User = event.getOAuth2User();
+            processOAuth2User(oAuth2User);
+        } catch (Exception e) {
+            log.error("OAuth2 사용자 저장 이벤트 처리 실패", e);
+        }
+    }
+
+    /**
+     * OAuth2 사용자 처리 (저장 또는 업데이트)
+     */
+    private void processOAuth2User(CustomOAuth2UserVO oAuth2User) {
+        String mbrType = convertProviderToMbrType(oAuth2User.getProvider());
+
+        // 이메일과 제공자로 기존 사용자 조회
+        MberVO existingUser = mberService.findByEmailAndProvider(oAuth2User.getEmail(), mbrType);
+
+        if (existingUser != null) {
+            updateExistingUser(existingUser, oAuth2User);
+        } else {
+            createNewUser(oAuth2User, mbrType);
+        }
+    }
+
+    /**
+     * 기존 사용자 정보 업데이트
+     */
+    private void updateExistingUser(MberVO existingUser, CustomOAuth2UserVO oAuth2User) {
+        try {
+            existingUser.setMbrNm(oAuth2User.getName());
+            // 프로필 이미지 URL이 있다면 업데이트
+            if (oAuth2User.getImageUrl() != null) {
+                // 필요시 프로필 이미지 필드 추가
+            }
+
+            mberService.updateOAuthUser(existingUser);
+            log.info("기존 OAuth2 사용자 정보 업데이트 완료: {}", existingUser.getEml());
+        } catch (Exception e) {
+            log.error("기존 OAuth2 사용자 업데이트 실패: {}", oAuth2User.getEmail(), e);
+        }
+    }
+
+    /**
+     * 새 OAuth2 사용자 생성
+     */
+    private void createNewUser(CustomOAuth2UserVO oAuth2User, String mbrType) {
+        try {
+            MberVO newUser = new MberVO();
+            newUser.setEml(oAuth2User.getEmail());
+            newUser.setMbrNm(oAuth2User.getName());
+            newUser.setNcnm(oAuth2User.getName());
+            newUser.setMbrType(mbrType);
+            newUser.setAuthorList(createDefaultAuthorities());
+
+            mberService.saveOAuthUser(newUser);
+            log.info("새 OAuth2 사용자 생성 완료: {}", newUser.getEml());
+        } catch (Exception e) {
+            log.error("새 OAuth2 사용자 생성 실패: {}", oAuth2User.getEmail(), e);
+        }
+    }
+
+    /**
+     * 기본 권한 생성
+     */
+    private List<MberAuthorVO> createDefaultAuthorities() {
+        List<MberAuthorVO> authorities = new ArrayList<>();
+        MberAuthorVO userRole = new MberAuthorVO();
+        userRole.setAuthrtCd("ROLE_USER");
+        authorities.add(userRole);
+        return authorities;
+    }
+
+    /**
+     * OAuth2 제공자를 회원 타입으로 변환
+     */
+    private String convertProviderToMbrType(String provider) {
+        return switch (provider.toLowerCase()) {
+            case "kakao" -> "K";
+            case "naver" -> "N";
+            case "google" -> "G";
+            case "facebook" -> "F";
+            default -> "S"; // 일반 회원
+        };
+    }
+}(파일 끝에 줄바꿈 문자 없음)
 
src/main/java/com/takensoft/common/oauth/service/CustomOAuth2UserService.java (added)
+++ src/main/java/com/takensoft/common/oauth/service/CustomOAuth2UserService.java
@@ -0,0 +1,27 @@
+package com.takensoft.common.oauth.service;
+
+import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
+import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.user.OAuth2User;
+
+/**
+ * @author takensoft
+ * @since 2025.05.22
+ * @modification
+ *     since    |    author    | description
+ *  2025.05.22  |  takensoft   | 최초 등록
+ *
+ * OAuth2 사용자 정보 서비스 인터페이스
+ */
+public interface CustomOAuth2UserService extends OAuth2UserService<OAuth2UserRequest, OAuth2User> {
+
+    /**
+     * @param userRequest - OAuth2 사용자 요청
+     * @return OAuth2User - OAuth2 사용자 정보
+     * @throws OAuth2AuthenticationException - OAuth2 인증 예외
+     *
+     * OAuth2 사용자 정보 로드
+     */
+    OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException;
+}(파일 끝에 줄바꿈 문자 없음)
 
src/main/java/com/takensoft/common/oauth/service/Impl/CustomOAuth2UserServiceImpl.java (added)
+++ src/main/java/com/takensoft/common/oauth/service/Impl/CustomOAuth2UserServiceImpl.java
@@ -0,0 +1,86 @@
+package com.takensoft.common.oauth.service.Impl;
+
+import com.takensoft.common.oauth.service.CustomOAuth2UserService;
+import com.takensoft.common.oauth.vo.CustomOAuth2UserVO;
+import com.takensoft.common.oauth.vo.OAuth2UserInfoVO;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
+import org.springframework.dao.DataAccessException;
+import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
+import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
+import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.user.OAuth2User;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author takensoft
+ * @since 2025.05.22
+ * @modification
+ *     since    |    author    | description
+ *  2025.05.22  |  takensoft   | 최초 등록
+ *
+ * OAuth2 사용자 정보 서비스 구현체
+ * EgovAbstractServiceImpl : 전자정부 상속
+ * CustomOAuth2UserService : OAuth2 사용자 정보 인터페이스 상속
+ * OAuth2UserService : Spring Security OAuth2 인터페이스 상속
+ */
+@Service("customOAuth2UserService")
+@RequiredArgsConstructor
+@Slf4j
+public class CustomOAuth2UserServiceImpl extends EgovAbstractServiceImpl
+        implements CustomOAuth2UserService, OAuth2UserService<OAuth2UserRequest, OAuth2User> {
+
+    private final DefaultOAuth2UserService delegate = new DefaultOAuth2UserService();
+
+    /**
+     * @param userRequest - OAuth2 사용자 요청
+     * @return OAuth2User - OAuth2 사용자 정보
+     * @throws OAuth2AuthenticationException - OAuth2 인증 예외 발생 시
+     * @throws DataAccessException - db 관련 예외 발생 시
+     * @throws Exception - 그 외 예외 발생 시
+     *
+     * OAuth2 사용자 정보 로드 (최소 처리)
+     */
+    @Override
+    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
+        try {
+            OAuth2User oAuth2User = delegate.loadUser(userRequest);
+            return processOAuth2User(userRequest, oAuth2User);
+        } catch (DataAccessException dae) {
+            log.error("OAuth2 사용자 처리 중 DB 오류 발생", dae);
+            throw new OAuth2AuthenticationException("OAuth2 사용자 처리 실패 - DB 오류");
+        } catch (Exception e) {
+            log.error("OAuth2 사용자 처리 중 오류 발생", e);
+            throw new OAuth2AuthenticationException("OAuth2 사용자 처리 실패");
+        }
+    }
+
+    /**
+     * @param userRequest - OAuth2 사용자 요청
+     * @param oAuth2User - OAuth2 사용자 정보
+     * @return OAuth2User - 처리된 OAuth2 사용자 정보
+     * @throws DataAccessException - db 관련 예외 발생 시
+     * @throws Exception - 그 외 예외 발생 시
+     *
+     * OAuth2 사용자 정보 처리 (단순히 CustomOAuth2User 객체만 생성)
+     */
+    private OAuth2User processOAuth2User(OAuth2UserRequest userRequest, OAuth2User oAuth2User) {
+        try {
+            // 제공업체 정보 추출
+            String registrationId = userRequest.getClientRegistration().getRegistrationId();
+
+            // 통합 OAuth2UserInfo 생성
+            OAuth2UserInfoVO oAuth2UserInfo = new OAuth2UserInfoVO(registrationId, oAuth2User.getAttributes());
+
+            // DB 작업 없이 단순히 CustomOAuth2User 객체만 반환
+            // 실제 사용자 저장/업데이트는 OAuth2SuccessHandler에서 처리
+            return new CustomOAuth2UserVO(oAuth2UserInfo, oAuth2User.getAttributes());
+        } catch (DataAccessException dae) {
+            throw dae;
+        } catch (Exception e) {
+            throw e;
+        }
+    }
+}(파일 끝에 줄바꿈 문자 없음)
 
src/main/java/com/takensoft/common/oauth/vo/CustomOAuth2UserVO.java (added)
+++ src/main/java/com/takensoft/common/oauth/vo/CustomOAuth2UserVO.java
@@ -0,0 +1,57 @@
+package com.takensoft.common.oauth.vo;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.oauth2.core.user.OAuth2User;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * @author takensoft
+ * @since 2025.05.22
+ * @modification
+ *     since    |    author    | description
+ *  2025.05.22  |  takensoft   | 최초 등록
+ *
+ * OAuth2 사용자 커스텀 클래스
+ */
+@RequiredArgsConstructor
+public class CustomOAuth2UserVO implements OAuth2User {
+
+    private final OAuth2UserInfoVO oAuth2UserInfoVO;
+    private final Map<String, Object> attributes;
+
+    @Override
+    public Map<String, Object> getAttributes() {
+        return attributes;
+    }
+
+    @Override
+    public Collection<? extends GrantedAuthority> getAuthorities() {
+        return Collections.emptyList(); // 권한 설정이 필요한 경우 여기서 처리
+    }
+
+    @Override
+    public String getName() {
+        return oAuth2UserInfoVO.getName();
+    }
+
+    // 추가 getter 메서드들
+    public String getId() {
+        return oAuth2UserInfoVO.getId();
+    }
+
+    public String getEmail() {
+        return oAuth2UserInfoVO.getEmail();
+    }
+
+    public String getImageUrl() {
+        return oAuth2UserInfoVO.getImageUrl();
+    }
+
+    public String getProvider() {
+        return oAuth2UserInfoVO.getProvider();
+    }
+}(파일 끝에 줄바꿈 문자 없음)
 
src/main/java/com/takensoft/common/oauth/vo/OAuth2UserInfoVO.java (added)
+++ src/main/java/com/takensoft/common/oauth/vo/OAuth2UserInfoVO.java
@@ -0,0 +1,75 @@
+package com.takensoft.common.oauth.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.util.Map;
+
+/**
+ * @author takensoft
+ * @since 2025.05.22
+ * @modification
+ *     since    |    author    | description
+ *  2025.05.22  |  takensoft   | 최초 등록
+ *
+ * OAuth2 사용자 정보 통합 VO
+ */
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+public class OAuth2UserInfoVO {
+
+    private Map<String, Object> attributes;    // OAuth2 응답 전체 데이터
+    private String provider;                   // 제공업체 (kakao, naver, google 등)
+    private String id;                         // 사용자 ID
+    private String name;                       // 사용자 이름
+    private String email;                      // 이메일
+    private String imageUrl;                   // 프로필 이미지 URL
+
+    // 제공업체별 데이터를 통일된 형태로 변환하는 생성자
+    public OAuth2UserInfoVO(String provider, Map<String, Object> attributes) {
+        this.provider = provider;
+        this.attributes = attributes;
+
+        switch (provider.toLowerCase()) {
+            case "kakao":
+                setKakaoUserInfo(attributes);
+                break;
+            case "naver":
+                setNaverUserInfo(attributes);
+                break;
+            case "google":
+                setGoogleUserInfo(attributes);
+                break;
+        }
+    }
+
+    private void setKakaoUserInfo(Map<String, Object> attributes) {
+        Map<String, Object> kakaoAccount = (Map<String, Object>) attributes.get("kakao_account");
+        Map<String, Object> profile = (Map<String, Object>) kakaoAccount.get("profile");
+
+        this.id = String.valueOf(attributes.get("id"));
+        this.name = (String) profile.get("nickname");
+        this.email = (String) kakaoAccount.get("email");
+        this.imageUrl = (String) profile.get("profile_image_url");
+    }
+
+    private void setNaverUserInfo(Map<String, Object> attributes) {
+        Map<String, Object> response = (Map<String, Object>) attributes.get("response");
+
+        this.id = (String) response.get("id");
+        this.name = (String) response.get("name");
+        this.email = (String) response.get("email");
+        this.imageUrl = (String) response.get("profile_image");
+    }
+
+    private void setGoogleUserInfo(Map<String, Object> attributes) {
+        this.id = (String) attributes.get("sub");
+        this.name = (String) attributes.get("name");
+        this.email = (String) attributes.get("email");
+        this.imageUrl = (String) attributes.get("picture");
+    }
+}(파일 끝에 줄바꿈 문자 없음)
 
src/main/java/com/takensoft/common/oauth/web/OAuth2Controller.java (added)
+++ src/main/java/com/takensoft/common/oauth/web/OAuth2Controller.java
@@ -0,0 +1,117 @@
+package com.takensoft.common.oauth.web;
+
+import com.takensoft.cms.mber.service.MberService;
+import com.takensoft.cms.mber.vo.MberVO;
+import com.takensoft.common.message.MessageCode;
+import com.takensoft.common.service.VerificationService;
+import com.takensoft.common.util.ResponseUtil;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpSession;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author takensoft
+ * @since 2025.05.22
+ * @modification
+ *     since    |    author    | description
+ *  2025.05.22  |  takensoft   | 최초 등록
+ *
+ * OAuth2 관련 컨트롤러
+ */
+@RestController
+@RequiredArgsConstructor
+@Slf4j
+@RequestMapping(value = "/oauth2")
+public class OAuth2Controller {
+
+    private final MberService mberService;
+    private final VerificationService verificationService;
+    private final ResponseUtil resUtil;
+
+    /**
+     * OAuth2 로그인 후 사용자 정보 조회
+     */
+    @GetMapping(value = "/user-info")
+    public ResponseEntity<?> getUserInfo(HttpServletRequest request) {
+        try {
+            // 현재 로그인된 사용자 ID 추출
+            String currentUserId = verificationService.getCurrentUserId();
+
+            if (currentUserId == null || currentUserId.isEmpty()) {
+                // 세션에서 OAuth2 정보 확인 (세션 모드인 경우)
+                HttpSession session = request.getSession(false);
+                if (session != null && session.getAttribute("oauth2User") != null) {
+                    return handleSessionOAuth2User(session);
+                }
+
+                return resUtil.errorRes(MessageCode.LOGIN_USER_NOT_FOUND);
+            }
+
+            // DB에서 사용자 정보 조회
+            HashMap<String, Object> params = new HashMap<>();
+            params.put("mbrId", currentUserId);
+            MberVO userInfo = mberService.findByMbr(params);
+
+            if (userInfo == null) {
+                return resUtil.errorRes(MessageCode.LOGIN_USER_NOT_FOUND);
+            }
+
+            // 응답 데이터 구성
+            Map<String, Object> response = createUserResponse(userInfo);
+            return resUtil.successRes(response, MessageCode.COMMON_SUCCESS);
+
+        } catch (Exception e) {
+            log.error("사용자 정보 조회 실패", e);
+            return resUtil.errorRes(MessageCode.COMMON_UNKNOWN_ERROR);
+        }
+    }
+
+    /**
+     * 세션의 OAuth2 사용자 정보 처리
+     */
+    private ResponseEntity<?> handleSessionOAuth2User(HttpSession session) {
+        try {
+            // 세션에서 OAuth2 사용자 정보 추출
+            // 이는 DB 저장이 완료되기 전의 임시 상태
+            Map<String, Object> tempResponse = new HashMap<>();
+            tempResponse.put("mbrId", "TEMP_OAUTH2");
+            tempResponse.put("mbrNm", "OAuth2 User");
+            tempResponse.put("roles", new String[]{"ROLE_USER"});
+            tempResponse.put("isTemporary", true);
+
+            return resUtil.successRes(tempResponse, MessageCode.COMMON_SUCCESS);
+        } catch (Exception e) {
+            log.error("세션 OAuth2 사용자 정보 처리 실패", e);
+            return resUtil.errorRes(MessageCode.COMMON_UNKNOWN_ERROR);
+        }
+    }
+
+    /**
+     * 사용자 응답 데이터 생성
+     */
+    private Map<String, Object> createUserResponse(MberVO userInfo) {
+        Map<String, Object> response = new HashMap<>();
+        response.put("mbrId", userInfo.getMbrId());
+        response.put("mbrNm", userInfo.getMbrNm());
+        response.put("eml", userInfo.getEml());
+        response.put("ncnm", userInfo.getNcnm());
+        response.put("mbrType", userInfo.getMbrType());
+
+        // 권한 정보 변환
+        String[] roles = userInfo.getAuthorList().stream()
+                .map(auth -> auth.getAuthrtCd())
+                .toArray(String[]::new);
+        response.put("roles", roles);
+        response.put("isTemporary", false);
+
+        return response;
+    }
+}(파일 끝에 줄바꿈 문자 없음)
src/main/resources/application.yml
--- src/main/resources/application.yml
+++ src/main/resources/application.yml
@@ -65,6 +65,55 @@
     verifyTime: 600000 # 인증가능 시간: 10분
     storeTime: 86400000 # 보관 시간: 24시간
 
+  # OAuth2 설정 추가
+  security:
+    oauth2:
+      client:
+        registration:
+          # 카카오 OAuth2 설정
+          kakao:
+            client-id: ${KAKAO_CLIENT_ID:bbaf1cd4db69d4b4a9be5b6917361ac3}
+            client-secret: ${KAKAO_CLIENT_SECRET:AvQ4lQ7HajbNa3AhuzOb979l5cMQDlEJ}
+            redirect-uri: ${KAKAO_REDIRECT_URI:http://localhost:9090/login/oauth2/code/kakao}
+            authorization-grant-type: authorization_code
+            scope: profile_nickname,profile_image,account_email
+            client-name: Kakao
+            client-authentication-method: client_secret_post
+
+          # 네이버 OAuth2 설정
+          naver:
+            client-id: ${NAVER_CLIENT_ID:Q7HdlZZhdCNjazYZtEwp}
+            client-secret: ${NAVER_CLIENT_SECRET:HkHD7Zedss}
+            redirect-uri: ${NAVER_REDIRECT_URI:http://localhost:9090/login/oauth2/code/naver}
+            authorization-grant-type: authorization_code
+            scope: name,email,profile_image
+            client-name: Naver
+
+          # 구글 OAuth2 설정
+          google:
+            client-id: ${GOOGLE_CLIENT_ID:AIzaSyB0FcOqHUlubnQzozH0G4fENpoq1pq3BxQ}
+            client-secret: ${GOOGLE_CLIENT_SECRET:your_google_client_secret_here}
+            redirect-uri: ${GOOGLE_REDIRECT_URI:http://localhost:9090/login/oauth2/code/google}
+            authorization-grant-type: authorization_code
+            scope: profile,email
+            client-name: Google
+
+        provider:
+          # 카카오 제공업체 설정
+          kakao:
+            authorization-uri: https://kauth.kakao.com/oauth/authorize
+            token-uri: https://kauth.kakao.com/oauth/token
+            user-info-uri: https://kapi.kakao.com/v2/user/me
+            user-name-attribute: id
+
+          # 네이버 제공업체 설정
+          naver:
+            authorization-uri: https://nid.naver.com/oauth2.0/authorize
+            token-uri: https://nid.naver.com/oauth2.0/token
+            user-info-uri: https://openapi.naver.com/v1/nid/me
+            user-name-attribute: response
+
+
 # Mybatis settings
 #mybatis:
 #  type-aliases-package: com.takensoft.**.**.vo, com.takensoft.**.**.dto, com.takensoft.common
src/main/resources/mybatis/mapper/mber/mber-SQL.xml
--- src/main/resources/mybatis/mapper/mber/mber-SQL.xml
+++ src/main/resources/mybatis/mapper/mber/mber-SQL.xml
@@ -214,4 +214,132 @@
         FROM mbr_authrt_info mai
         WHERE mai.mbr_id = #{mbrId}
     </select>
+
+
+    <!-- 이메일로만 사용자 조회 -->
+    <select id="findByEmail" parameterType="String" resultType="MberVO">
+    SELECT
+        m.mbr_id as mbrId,
+        m.lgn_id as lgnId,
+        m.mbr_nm as mbrNm,
+        m.ncnm,
+        m.pswd,
+        m.mbl_telno as mblTelno,
+        m.telno,
+        m.eml,
+        m.zip,
+        m.addr,
+        m.daddr,
+        m.mbr_stts as mbrStts,
+        m.use_yn as useYn,
+        m.cntrl_dt as cntrlDt,
+        m.cntrl_rsn as cntrlRsn,
+        m.sms_rcptn_agre_yn as smsRcptnAgreYn,
+        m.eml_rcptn_agre_yn as emlRcptnAgreYn,
+        m.prvc_rls_yn as prvcRlsYn,
+        m.mbr_type as mbrType,
+        m.pswd_chg_dt as pswdChgDt,
+        m.frst_reg_ip as frstRegIp,
+        m.sys_pvsn_yn as sysPvsnYn,
+        m.rgtr,
+        m.reg_dt as regDt,
+        m.mdfr,
+        m.mdfcn_dt as mdfcnDt
+    FROM mbr_info m
+    WHERE m.eml = #{email}
+    AND m.use_yn = 'Y'
+    AND m.mbr_stts = '1'
+</select>
+
+    <!-- 이메일과 회원 유형으로 사용자 조회 -->
+    <select id="findByEmailAndProvider" parameterType="map" resultType="MberVO">
+        SELECT
+            m.mbr_id as mbrId,
+            m.lgn_id as lgnId,
+            m.mbr_nm as mbrNm,
+            m.ncnm,
+            m.pswd,
+            m.mbl_telno as mblTelno,
+            m.telno,
+            m.eml,
+            m.zip,
+            m.addr,
+            m.daddr,
+            m.mbr_stts as mbrStts,
+            m.use_yn as useYn,
+            m.cntrl_dt as cntrlDt,
+            m.cntrl_rsn as cntrlRsn,
+            m.sms_rcptn_agre_yn as smsRcptnAgreYn,
+            m.eml_rcptn_agre_yn as emlRcptnAgreYn,
+            m.prvc_rls_yn as prvcRlsYn,
+            m.mbr_type as mbrType,
+            m.pswd_chg_dt as pswdChgDt,
+            m.frst_reg_ip as frstRegIp,
+            m.sys_pvsn_yn as sysPvsnYn,
+            m.rgtr,
+            m.reg_dt as regDt,
+            m.mdfr,
+            m.mdfcn_dt as mdfcnDt
+        FROM mbr_info m
+        WHERE m.eml = #{0}
+        AND m.mbr_type = #{1}
+        AND m.use_yn = 'Y'
+        AND m.mbr_stts = '1'
+    </select>
+
+        <!-- OAuth2 사용자 저장 -->
+        <insert id="saveOAuthUser" parameterType="MberVO">
+        INSERT INTO mbr_info (
+            mbr_id,
+            lgn_id,
+            mbr_nm,
+            ncnm,
+            eml,
+            mbr_stts,
+            use_yn,
+            sms_rcptn_agre_yn,
+            eml_rcptn_agre_yn,
+            prvc_rls_yn,
+            mbr_type,
+            sys_pvsn_yn,
+            rgtr,
+            reg_dt
+        ) VALUES (
+            #{mbrId},
+            #{lgnId},
+            #{mbrNm},
+            #{ncnm},
+            #{eml},
+            #{mbrStts},
+            #{useYn},
+            'N',
+            'N',
+            'N',
+            #{mbrType},
+            #{sysPvsnYn},
+            #{rgtr},
+            NOW()
+        )
+    </insert>
+
+        <!-- OAuth2 사용자 정보 업데이트 -->
+        <update id="updateOAuthUser" parameterType="MberVO">
+        UPDATE mbr_info
+        SET
+            mbr_nm = #{mbrNm},
+            ncnm = #{ncnm},
+            mdfr = #{mdfr},
+            mdfcn_dt = NOW()
+        WHERE mbr_id = #{mbrId}
+    </update>
+
+        <!-- 기존 계정에 OAuth2 정보 연동 -->
+        <update id="linkOAuth2Account" parameterType="MberVO">
+        UPDATE mbr_info
+        SET
+            mbr_type = #{mbrType},
+            mdfr = #{mdfr},
+            mdfcn_dt = NOW()
+        WHERE mbr_id = #{mbrId}
+    </update>
 </mapper>
(파일 끝에 줄바꿈 문자 없음)
Add a comment
List