hmkim 05-26
250526 김혜민 oauth2 수정
@9e42556164f5d1da32dc50757f9feb48782155f5
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
@@ -77,7 +77,7 @@
 
     /**
      * @param email - 이메일
-     * @param provider - OAuth2 제공자
+     * @param mbrType - OAuth2 회원 유형 (K, N, G, F, S)
      * @return MberVO - OAuth2 사용자 정보
      *
      * 이메일과 제공자로 사용자 조회
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
@@ -23,6 +23,7 @@
 
 import jakarta.servlet.http.HttpServletRequest;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 
@@ -59,9 +60,9 @@
     @Transactional(readOnly = true)
     public UserDetails loadUserByUsername(String username){
         try {
-             UserDetails userDetails = mberDAO.findByMberSecurity(username);
+            UserDetails userDetails = mberDAO.findByMberSecurity(username);
 
-             return userDetails;
+            return userDetails;
         } catch (UsernameNotFoundException Unfe) {
             throw Unfe;
         } catch (Exception e) {
@@ -109,15 +110,21 @@
     @Transactional(rollbackFor = Exception.class)
     public HashMap<String, Object> userJoin(HttpServletRequest req,  JoinDTO joinDTO){
         try {
-            // 회원 아이디 생성
-            String mbrId = mberIdgn.getNextStringId();
-            joinDTO.setMbrId(mbrId);
+            // 회원 아이디 생성 (이미 설정된 경우 건너뛰기)
+            if (joinDTO.getMbrId() == null || joinDTO.getMbrId().isEmpty()) {
+                String mbrId = mberIdgn.getNextStringId();
+                joinDTO.setMbrId(mbrId);
+            }
 
             // 아이디 소문자 변환
-            joinDTO.setLgnId(joinDTO.getLgnId().toLowerCase());
+            if (joinDTO.getLgnId() != null && !joinDTO.getLgnId().isEmpty()) {
+                joinDTO.setLgnId(joinDTO.getLgnId().toLowerCase());
+            }
 
-            // 비밀번호 암호화
-            joinDTO.setPswd(bCryptPasswordEncoder.encode(joinDTO.getPswd()));
+            // 비밀번호 암호화 (OAuth2는 비밀번호 없음)
+            if (joinDTO.getPswd() != null && !joinDTO.getPswd().isEmpty()) {
+                joinDTO.setPswd(bCryptPasswordEncoder.encode(joinDTO.getPswd()));
+            }
 
             // 연락처 암호화
             if(joinDTO.getMblTelno() != null && !joinDTO.getMblTelno().equals("")) {
@@ -128,15 +135,21 @@
             }
 
             // 아이피 조회 및 등록
-            joinDTO.setFrstRegIp(httpRequestUtil.getIp(req));
+            if (req != null) {
+                joinDTO.setFrstRegIp(httpRequestUtil.getIp(req));
+            } else {
+                joinDTO.setFrstRegIp("0.0.0.0"); // OAuth2의 경우 기본값
+            }
 
             // 등록된 토큰에서 사용자 정보 조회
-            String writer = verificationService.getCurrentUserId();
+            String writer = joinDTO.getRgtr();
             if (writer == null || writer.isEmpty()) {
-                throw new CustomNotFoundException("사용자 정보 조회에 실패했습니다.");
+                writer = verificationService.getCurrentUserId();
+                if (writer == null || writer.isEmpty()) {
+                    throw new CustomNotFoundException("사용자 정보 조회에 실패했습니다.");
+                }
+                joinDTO.setRgtr(writer);
             }
-            // 작성자 등록
-            joinDTO.setRgtr(writer);
 
             // 회원정보 등록
             HashMap<String, Object> result = new HashMap<>();
@@ -145,7 +158,7 @@
                 throw new CustomInsertFailException("회원 정보 등록에 실패했습니다.");
             }
 
-            result.put("mbrId", mbrId);
+            result.put("mbrId", joinDTO.getMbrId());
 
             // 권한 등록
             int authorResult = 0;
@@ -327,45 +340,23 @@
      * @throws DataAccessException - db 관련 예외 발생 시
      * @throws Exception - 그 외 예외 발생 시
      *
-     * OAuth2 사용자 저장
+     * OAuth2 사용자 저장 - JoinDTO를 활용하여 기존 로직 재사용
      */
     @Override
     @Transactional(rollbackFor = Exception.class)
     public MberVO saveOAuthUser(MberVO user) {
         try {
-            // 회원 아이디 생성
-            String mbrId = mberIdgn.getNextStringId();
-            user.setMbrId(mbrId);
+            // OAuth2 사용자를 JoinDTO로 변환하여 기존 검증된 로직 활용
+            JoinDTO oauthJoinDTO = createOAuthJoinDTO(user);
 
-            // 기본값 설정
-            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");
+            // 기존 userJoin 메서드 활용 (검증된 로직)
+            HashMap<String, Object> result = userJoin(null, oauthJoinDTO);
 
-            // 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 사용자 권한 저장에 실패했습니다.");
-                    }
-                }
-            }
+            // 생성된 회원 ID 설정
+            user.setMbrId(result.get("mbrId").toString());
 
             return user;
+
         } catch (DataAccessException dae) {
             throw dae;
         } catch (Exception e) {
@@ -374,6 +365,46 @@
     }
 
     /**
+     * OAuth2 사용자 정보를 JoinDTO로 변환
+     * @param user OAuth2 사용자 정보
+     * @return JoinDTO 변환된 회원가입 정보
+     */
+    private JoinDTO createOAuthJoinDTO(MberVO user) {
+        // 기본 권한 설정
+        List<MberAuthorVO> defaultAuthorities = createDefaultAuthorities();
+
+        JoinDTO joinDTO = new JoinDTO();
+        joinDTO.setMbrNm(user.getMbrNm());
+        joinDTO.setNcnm(user.getNcnm() != null ? user.getNcnm() : user.getMbrNm()); // 닉네임이 없으면 이름 사용
+        joinDTO.setLgnId(user.getEml().toLowerCase()); // 이메일을 로그인 ID로 사용
+        joinDTO.setEml(user.getEml());
+        joinDTO.setPswd("OAUTH2_NO_PASSWORD"); // OAuth2 사용자용 더미 비밀번호 (암호화됨)
+        joinDTO.setMbrStts("1");           // 승인 상태
+        joinDTO.setUseYn("Y");             // 사용 (문자열)
+        joinDTO.setSmsRcptnAgreYn("N");    // SMS 수신 거부 (기본값)
+        joinDTO.setEmlRcptnAgreYn("N");    // 이메일 수신 거부 (기본값)
+        joinDTO.setPrvcRlsYn("N");         // 개인정보 공개 거부 (기본값)
+        joinDTO.setMbrType(user.getMbrType()); // OAuth2 제공자 타입 (K, N, G, F)
+        joinDTO.setSysPvsnYn("1");         // 사용자 데이터 (문자열)
+        joinDTO.setRgtr("OAUTH2_SYSTEM");  // OAuth2 시스템 등록자
+        joinDTO.setAuthorList(user.getAuthorList() != null ? user.getAuthorList() : defaultAuthorities);
+
+        return joinDTO;
+    }
+
+    /**
+     * OAuth2 사용자 기본 권한 생성
+     * @return List<MberAuthorVO> 기본 권한 목록
+     */
+    private List<MberAuthorVO> createDefaultAuthorities() {
+        List<MberAuthorVO> authorities = new ArrayList<>();
+        MberAuthorVO userRole = new MberAuthorVO();
+        userRole.setAuthrtCd("ROLE_USER");
+        authorities.add(userRole);
+        return authorities;
+    }
+
+    /**
      * @param user - OAuth2 사용자 정보
      * @return MberVO - 업데이트된 사용자 정보
      * @throws CustomUpdateFailException - 사용자 업데이트 실패 예외 발생 시
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
@@ -170,9 +170,24 @@
         );
 
         http.authorizeHttpRequests((auth) -> auth
-                .requestMatchers("/", "/mbr/**", "/refresh/**", "/sys/**", "/editFileUpload/**", "/fileUpload/**", "/oauth2/**", "/login/oauth2/**").permitAll() // 회원, 토큰, 시스템 제공, 파일, OAuth2 접근 모두 허용
+                .requestMatchers("/", "/mbr/**", "/refresh/**", "/sys/**", "/editFileUpload/**", "/fileUpload/**", "/oauth2/**", "/login/oauth2/**", "/.well-known/**").permitAll() // 회원, 토큰, 시스템 제공, 파일, OAuth2 접근 모두 허용
                 .requestMatchers("/admin/**").hasRole("ADMIN") // 관리자 페이지는 ADMIN 권한을 가진 사용자만 접근 가능
                 .anyRequest().authenticated() // 그 외에는 로그인한 사용자만 접근 가능
+        );
+
+        // OAuth2 로그인 설정
+        http.oauth2Login(oauth2 -> oauth2
+                .authorizationEndpoint(authorization -> authorization
+                        .baseUri("/oauth2/authorization")
+                )
+                .redirectionEndpoint(redirection -> redirection
+                        .baseUri("/login/oauth2/code/*")
+                )
+                .userInfoEndpoint(userInfo -> userInfo
+                        .userService(customOAuth2UserServiceImpl)
+                )
+                .successHandler(oAuth2AuthenticationSuccessHandler)
+                .failureHandler(oAuth2AuthenticationFailureHandler)
         );
 
         // Context Path 검증 필터
@@ -188,14 +203,6 @@
         http.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil, refreshTokenService, lgnHstryService, httpRequestUtil,
                 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/filter/AccesFilter.java
--- src/main/java/com/takensoft/common/filter/AccesFilter.java
+++ src/main/java/com/takensoft/common/filter/AccesFilter.java
@@ -58,6 +58,14 @@
      */
     @Override
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+
+        String requestURI = request.getRequestURI();
+
+        // OAuth2 관련 경로는 접근 제어 제외
+        if (isOAuth2Request(requestURI)) {
+            filterChain.doFilter(request, response);
+            return;
+        }
         try {
             // 아이피 정보
             String ipAdrs = httpRequestUtil.getIp(request);
@@ -105,6 +113,15 @@
     }
 
     /**
+     * OAuth2 관련 요청인지 확인
+     */
+    private boolean isOAuth2Request(String requestURI) {
+        return requestURI.startsWith("/oauth2/") ||
+                requestURI.startsWith("/login/oauth2/") ||
+                requestURI.startsWith("/oauth/");
+    }
+
+    /**
      * @param accesCtrlList 접근 제어 정보 리스트
      * @param req HttpServletRequest 객체
      * @return boolean 요청 URI에 따른 접근 제어 여부
src/main/java/com/takensoft/common/filter/JWTFilter.java
--- src/main/java/com/takensoft/common/filter/JWTFilter.java
+++ src/main/java/com/takensoft/common/filter/JWTFilter.java
@@ -71,6 +71,13 @@
      */
     @Override
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+        String requestURI = request.getRequestURI();
+
+        // OAuth2 관련 경로는 JWT 검증 제외
+        if (isOAuth2Request(requestURI)) {
+            filterChain.doFilter(request, response);
+            return;
+        }
         try {
             String loginMode = loginModeService.getLoginMode();
             String accessToken = resolveToken(request, loginMode);
@@ -120,6 +127,16 @@
         }
     }
 
+    /**
+     * OAuth2 관련 요청인지 확인
+     */
+    private boolean isOAuth2Request(String requestURI) {
+        return requestURI.startsWith("/oauth2/") ||
+                requestURI.startsWith("/login/oauth2/") ||
+                requestURI.startsWith("/oauth/");
+    }
+
+
     private boolean isTokenValid(String mbrId, String accessToken) {
         String storedToken = redisTemplate.opsForValue().get("jwt:" + mbrId);
         return storedToken == null || storedToken.equals(accessToken);
src/main/java/com/takensoft/common/oauth/handler/OAuth2AuthenticationSuccessHandler.java
--- src/main/java/com/takensoft/common/oauth/handler/OAuth2AuthenticationSuccessHandler.java
+++ src/main/java/com/takensoft/common/oauth/handler/OAuth2AuthenticationSuccessHandler.java
@@ -54,7 +54,8 @@
     private String FRONT_URL;
 
     @Override
-    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException{
+    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
+                                        Authentication authentication) throws IOException, ServletException {
 
         CustomOAuth2UserVO oAuth2User = (CustomOAuth2UserVO) authentication.getPrincipal();
 
@@ -73,13 +74,19 @@
             String tempUserId = createTempUserId(oAuth2User);
             processLoginByMode(request, response, oAuth2User, tempUserId, loginMode);
 
-            // 4. 프론트엔드 OAuth 콜백 페이지로 리다이렉트
-            String redirectUrl = FRONT_URL + "/oauth/callback";
+            // 4. 캐시 방지 헤더 설정
+            response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+            response.setHeader("Pragma", "no-cache");
+            response.setHeader("Expires", "0");
+
+            // 5. 프론트엔드 로그인 페이지로 리다이렉트 (OAuth 성공 파라미터 + 타임스탬프)
+            long timestamp = System.currentTimeMillis();
+            String redirectUrl = FRONT_URL + "/login.page?oauth_success=true&t=" + timestamp;
             getRedirectStrategy().sendRedirect(request, response, redirectUrl);
 
         } catch (Exception e) {
             log.error("OAuth2 로그인 처리 중 오류 발생", e);
-            String errorUrl = FRONT_URL + "/login?error=oauth2_processing_failed";
+            String errorUrl = FRONT_URL + "/login.page?error=oauth2_processing_failed";
             getRedirectStrategy().sendRedirect(request, response, errorUrl);
         }
     }
@@ -113,7 +120,9 @@
     /**
      * 로그인 모드에 따른 처리
      */
-    private void processLoginByMode(HttpServletRequest request, HttpServletResponse response, CustomOAuth2UserVO oAuth2User, String tempUserId, String loginMode){
+    private void processLoginByMode(HttpServletRequest request, HttpServletResponse response,
+                                    CustomOAuth2UserVO oAuth2User, String tempUserId, String loginMode)
+            throws IOException {
 
         // JWT 토큰 생성
         String accessToken = jwtUtil.createJwt(
@@ -139,7 +148,8 @@
     /**
      * 세션 모드 처리
      */
-    private void processSessionMode(HttpServletRequest request, String tempUserId,String accessToken, CustomOAuth2UserVO oAuth2User) {
+    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);
src/main/java/com/takensoft/common/oauth/web/OAuth2Controller.java
--- src/main/java/com/takensoft/common/oauth/web/OAuth2Controller.java
+++ src/main/java/com/takensoft/common/oauth/web/OAuth2Controller.java
@@ -4,17 +4,24 @@
 import com.takensoft.cms.mber.vo.MberVO;
 import com.takensoft.common.message.MessageCode;
 import com.takensoft.common.service.VerificationService;
+import com.takensoft.common.util.HttpRequestUtil;
 import com.takensoft.common.util.ResponseUtil;
 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.http.ResponseEntity;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.io.IOException;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -23,8 +30,9 @@
  * @modification
  *     since    |    author    | description
  *  2025.05.22  |  takensoft   | 최초 등록
+ *  2025.05.26  |  takensoft   | OAuth 리다이렉트 기능 추가
  *
- * OAuth2 관련 컨트롤러
+ * OAuth2 관련 통합 컨트롤러
  */
 @RestController
 @RequiredArgsConstructor
@@ -35,6 +43,63 @@
     private final MberService mberService;
     private final VerificationService verificationService;
     private final ResponseUtil resUtil;
+    private final HttpRequestUtil httpRequestUtil;
+
+    @Value("${front.url}")
+    private String FRONT_URL;
+
+    // 지원하는 OAuth 제공자 목록
+    private static final List<String> SUPPORTED_PROVIDERS = Arrays.asList("kakao", "naver", "google");
+
+    /**
+     * OAuth 로그인 리다이렉트 처리
+     * 프론트엔드에서 provider 정보를 받아 검증 후 OAuth2 서버로 리다이렉트
+     */
+    @GetMapping("/login")
+    public void redirectToOAuth(@RequestParam String provider,
+                                HttpServletRequest request,
+                                HttpServletResponse response) throws IOException {
+
+        String clientIP = httpRequestUtil.getIp(request);
+        String userAgent = httpRequestUtil.getUserAgent(request);
+
+        log.info("=== OAuth 로그인 시도 ===");
+        log.info("Provider: {}", provider);
+        log.info("Client IP: {}", clientIP);
+        log.info("User Agent: {}", userAgent);
+
+        try {
+            // 1. Provider 유효성 검증
+            validateProvider(provider);
+
+            // 2. 보안 검증 (필요시 추가)
+            validateSecurity(request);
+
+            // 3. CORS 헤더 설정 (브라우저 보안 문제 해결)
+            response.setHeader("Access-Control-Allow-Origin", FRONT_URL);
+            response.setHeader("Access-Control-Allow-Credentials", "true");
+            response.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
+            response.setHeader("Access-Control-Allow-Headers", "*");
+
+            // 4. OAuth2 Authorization Server로 리다이렉트
+            String redirectUrl = "/oauth2/authorization/" + provider;
+            log.info("OAuth 리다이렉트 URL: {}", redirectUrl);
+
+            // 리다이렉트 상태 코드를 명시적으로 설정
+            response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
+            response.sendRedirect(redirectUrl);
+
+        } catch (IllegalArgumentException e) {
+            log.error("OAuth 로그인 실패 - 잘못된 Provider: {}", provider);
+            handleError(response, "invalid_provider", e.getMessage());
+        } catch (SecurityException e) {
+            log.error("OAuth 로그인 실패 - 보안 검증 실패: {}", e.getMessage());
+            handleError(response, "security_check_failed", e.getMessage());
+        } catch (Exception e) {
+            log.error("OAuth 로그인 실패 - 시스템 오류", e);
+            handleError(response, "system_error", "OAuth 로그인 중 오류가 발생했습니다.");
+        }
+    }
 
     /**
      * OAuth2 로그인 후 사용자 정보 조회
@@ -65,7 +130,7 @@
             }
 
             // 응답 데이터 구성
-            Map<String, Object> response = createUserResponse(userInfo);
+            HashMap<String, Object> response = createUserResponse(userInfo);
             return resUtil.successRes(response, MessageCode.COMMON_SUCCESS);
 
         } catch (Exception e) {
@@ -75,13 +140,66 @@
     }
 
     /**
+     * 지원하는 OAuth 제공자 목록 조회 API
+     */
+    @GetMapping("/providers")
+    public ResponseEntity<?> getSupportedProviders() {
+        log.info("지원하는 OAuth 제공자 목록 조회");
+        return resUtil.successRes(SUPPORTED_PROVIDERS, MessageCode.COMMON_SUCCESS);
+    }
+
+    /**
+     * OAuth 제공자 유효성 검증
+     */
+    private void validateProvider(String provider) {
+        if (provider == null || provider.trim().isEmpty()) {
+            throw new IllegalArgumentException("OAuth 제공자가 지정되지 않았습니다.");
+        }
+
+        if (!SUPPORTED_PROVIDERS.contains(provider.toLowerCase())) {
+            throw new IllegalArgumentException("지원하지 않는 OAuth 제공자입니다: " + provider);
+        }
+
+        // TODO: 동적 설정으로 특정 제공자 비활성화 체크
+        // if (!oauthConfigService.isProviderEnabled(provider)) {
+        //     throw new IllegalArgumentException(provider + " 로그인이 일시 중단되었습니다.");
+        // }
+    }
+
+    /**
+     * 보안 검증 (필요시 확장)
+     */
+    private void validateSecurity(HttpServletRequest request) {
+        // TODO: 필요시 보안 검증 로직 추가
+        // 예: IP 화이트리스트, Rate Limiting, 사용자 상태 체크 등
+
+        String clientIP = httpRequestUtil.getIp(request);
+
+        // 예시: 로컬 개발 환경이 아닌 경우 추가 검증
+        if (!"127.0.0.1".equals(clientIP) && !"::1".equals(clientIP)) {
+            // 운영 환경 보안 검증 로직
+        }
+    }
+
+    /**
+     * 에러 발생 시 프론트엔드로 리다이렉트
+     */
+    private void handleError(HttpServletResponse response, String errorCode, String errorMessage) throws IOException {
+        String encodedMessage = java.net.URLEncoder.encode(errorMessage, "UTF-8");
+        String errorUrl = String.format("%s/login?error=%s&message=%s", FRONT_URL, errorCode, encodedMessage);
+
+        log.info("에러 리다이렉트 URL: {}", errorUrl);
+        response.sendRedirect(errorUrl);
+    }
+
+    /**
      * 세션의 OAuth2 사용자 정보 처리
      */
     private ResponseEntity<?> handleSessionOAuth2User(HttpSession session) {
         try {
             // 세션에서 OAuth2 사용자 정보 추출
             // 이는 DB 저장이 완료되기 전의 임시 상태
-            Map<String, Object> tempResponse = new HashMap<>();
+            HashMap<String, Object> tempResponse = new HashMap<>();
             tempResponse.put("mbrId", "TEMP_OAUTH2");
             tempResponse.put("mbrNm", "OAuth2 User");
             tempResponse.put("roles", new String[]{"ROLE_USER"});
@@ -97,8 +215,8 @@
     /**
      * 사용자 응답 데이터 생성
      */
-    private Map<String, Object> createUserResponse(MberVO userInfo) {
-        Map<String, Object> response = new HashMap<>();
+    private HashMap<String, Object> createUserResponse(MberVO userInfo) {
+        HashMap<String, Object> response = new HashMap<>();
         response.put("mbrId", userInfo.getMbrId());
         response.put("mbrNm", userInfo.getMbrNm());
         response.put("eml", userInfo.getEml());
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
@@ -218,41 +218,6 @@
 
     <!-- 이메일로만 사용자 조회 -->
     <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,
@@ -281,14 +246,32 @@
             m.mdfr,
             m.mdfcn_dt as mdfcnDt
         FROM mbr_info m
-        WHERE m.eml = #{0}
-        AND m.mbr_type = #{1}
+        WHERE m.eml = #{email}
         AND m.use_yn = 'Y'
         AND m.mbr_stts = '1'
     </select>
 
+    <!-- 이메일과 회원 유형으로 사용자 조회 -->
+    <select id="findByEmailAndProvider" parameterType="map" resultMap="mberMap">
+        <include refid="selectMber" />
+        WHERE mi.eml = #{email}
+        AND mi.mbr_type = #{mbrType}
+        AND mi.use_yn = 'Y'
+        AND mi.mbr_stts = '1'
+    </select>
+
+    <!-- 회원 ID로 권한 목록 조회 -->
+    <select id="findAuthoritiesByMbrId" parameterType="String" resultMap="authMap">
+        SELECT mai.mbr_id
+             , mai.authrt_cd
+             , mai.rgtr
+             , TO_CHAR(mai.reg_dt, 'YYYY-MM-DD HH24:MI') AS reg_dt
+        FROM mbr_authrt_info mai
+        WHERE mai.mbr_id = #{mbrId}
+    </select>
+
         <!-- OAuth2 사용자 저장 -->
-        <insert id="saveOAuthUser" parameterType="MberVO">
+    <insert id="saveOAuthUser" parameterType="MberVO">
         INSERT INTO mbr_info (
             mbr_id,
             lgn_id,
Add a comment
List