package com.takensoft.common.oauth.handler; import com.takensoft.cms.loginPolicy.service.LoginModeService; import com.takensoft.cms.mber.service.LgnHstryService; import com.takensoft.cms.mber.service.UnifiedLoginService; import com.takensoft.cms.mber.vo.LgnHstryVO; import com.takensoft.cms.mber.vo.MberVO; import com.takensoft.common.oauth.vo.CustomOAuth2UserVO; import com.takensoft.common.util.HttpRequestUtil; import com.takensoft.common.util.LoginUtil; 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.Authentication; import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; import java.io.IOException; import java.net.URLEncoder; import java.util.Map; /** * @author takensoft * @since 2025.05.22 * @modification * since | author | description * 2025.05.22 | takensoft | 최초 등록 * 2025.05.28 | takensoft | 통합 로그인 적용 * 2025.05.29 | takensoft | OAuth2 통합 문제 해결 * 2025.06.02 | takensoft | 세션 모드 중복로그인 처리 개선 * 2025.06.09 | takensoft | OIDC 타입 캐스팅 문제 해결 * * OAuth2 로그인 성공 핸들러 */ @Slf4j @Component @RequiredArgsConstructor public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { private final UnifiedLoginService unifiedLoginService; private final LgnHstryService lgnHstryService; private final HttpRequestUtil httpRequestUtil; private final LoginUtil loginUtil; private final LoginModeService loginModeService; @Value("${front.url}") private String frontUrl; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { try { // OAuth2User 타입 확인 및 정보 추출 OAuth2UserInfo userInfo = extractUserInfo(authentication); if (userInfo == null) { handleOAuth2Error(response, new Exception("사용자 정보 추출 실패")); return; } // 현재 설정된 로그인 모드 확인 String currentLoginMode = loginModeService.getLoginMode(); // 통합 로그인 서비스를 통한 OAuth2 사용자 처리 MberVO mber = unifiedLoginService.processOAuth2User( userInfo.email, unifiedLoginService.convertProviderToMbrType(userInfo.provider), userInfo.id, userInfo.name, request ); // OAuth2 로그인 이력 저장 saveLoginHistory(request, mber, userInfo.provider); request.setAttribute("loginType", "OAUTH2"); // LoginUtil을 통한 통합 로그인 처리 loginUtil.successLogin(mber, request, response); // OAuth2 성공 후 프론트엔드로 리다이렉트 String redirectUrl = String.format("%s/login.page?oauth_success=true&loginMode=%s", frontUrl, currentLoginMode); getRedirectStrategy().sendRedirect(request, response, redirectUrl); } catch (Exception e) { handleOAuth2Error(response, e); } } /** * OAuth2User에서 사용자 정보 추출 (타입별 처리) */ private OAuth2UserInfo extractUserInfo(Authentication authentication) { Object principal = authentication.getPrincipal(); try { if (principal instanceof CustomOAuth2UserVO) { // 커스텀 OAuth2 사용자 CustomOAuth2UserVO customUser = (CustomOAuth2UserVO) principal; return new OAuth2UserInfo( customUser.getProvider(), customUser.getId(), customUser.getName(), customUser.getEmail() ); } else if (principal instanceof OidcUser) { // OIDC 사용자 (구글) OidcUser oidcUser = (OidcUser) principal; return extractOidcUserInfo(oidcUser, authentication); } else if (principal instanceof OAuth2User) { // 일반 OAuth2 사용자 OAuth2User oauth2User = (OAuth2User) principal; return extractOAuth2UserInfo(oauth2User, authentication); } else { return null; } } catch (Exception e) { return null; } } /** * OIDC 사용자 정보 추출 (구글) */ private OAuth2UserInfo extractOidcUserInfo(OidcUser oidcUser, Authentication authentication) { try { String provider = determineProvider(authentication); Map attributes = oidcUser.getAttributes(); return new OAuth2UserInfo( provider, oidcUser.getSubject(), // OIDC의 subject가 사용자 ID oidcUser.getFullName() != null ? oidcUser.getFullName() : oidcUser.getGivenName(), oidcUser.getEmail() ); } catch (Exception e) { return null; } } /** * 일반 OAuth2 사용자 정보 추출 */ private OAuth2UserInfo extractOAuth2UserInfo(OAuth2User oauth2User, Authentication authentication) { try { String provider = determineProvider(authentication); Map attributes = oauth2User.getAttributes(); String id = null; String name = null; String email = null; // 제공자별 정보 추출 switch (provider.toLowerCase()) { case "kakao": id = String.valueOf(attributes.get("id")); Map kakaoAccount = (Map) attributes.get("kakao_account"); if (kakaoAccount != null) { email = (String) kakaoAccount.get("email"); Map profile = (Map) kakaoAccount.get("profile"); if (profile != null) { name = (String) profile.get("nickname"); } } break; case "naver": Map naverResponse = (Map) attributes.get("response"); if (naverResponse != null) { id = (String) naverResponse.get("id"); name = (String) naverResponse.get("name"); email = (String) naverResponse.get("email"); } break; case "google": id = (String) attributes.get("sub"); name = (String) attributes.get("name"); email = (String) attributes.get("email"); break; } return new OAuth2UserInfo(provider, id, name, email); } catch (Exception e) { return null; } } /** * 제공자 결정 */ private String determineProvider(Authentication authentication) { // 클라이언트 등록 ID에서 제공자 결정 String name = authentication.getName(); if (name != null) { if (name.contains("google")) return "google"; if (name.contains("kakao")) return "kakao"; if (name.contains("naver")) return "naver"; } // 기본값 return "google"; } /** * 로그인 이력 저장 - OAuth2 전용 */ private void saveLoginHistory(HttpServletRequest request, MberVO mber, String provider) { String userAgent = httpRequestUtil.getUserAgent(request); LgnHstryVO loginHistory = new LgnHstryVO(); loginHistory.setLgnId(mber.getLgnId()); loginHistory.setLgnType(mber.getAuthorities().stream().anyMatch(r -> r.getAuthority().equals("ROLE_ADMIN")) ? "0" : "1"); loginHistory.setCntnIp(httpRequestUtil.getIp(request)); loginHistory.setCntnOperSys(httpRequestUtil.getOS(userAgent)); loginHistory.setDeviceNm(httpRequestUtil.getDevice(userAgent)); loginHistory.setBrwsrNm(httpRequestUtil.getBrowser(userAgent)); lgnHstryService.LgnHstrySave(loginHistory); } /** * OAuth2 오류 처리 */ private void handleOAuth2Error(HttpServletResponse response, Exception e) throws IOException { String message = URLEncoder.encode("OAuth 로그인에 실패했습니다.", "UTF-8"); String errorUrl = String.format("%s/login.page?error=oauth2_failed&message=%s", frontUrl, message); getRedirectStrategy().sendRedirect(null, response, errorUrl); } /** * OAuth2 사용자 정보 내부 클래스 */ private static class OAuth2UserInfo { final String provider; final String id; final String name; final String email; OAuth2UserInfo(String provider, String id, String name, String email) { this.provider = provider; this.id = id; this.name = name; this.email = email; } } }