
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
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.client.authentication.OAuth2AuthenticationToken;
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 {
log.info("OAuth2 로그인 성공 핸들러 시작");
// OAuth2User 타입 확인 및 정보 추출
OAuth2UserInfo userInfo = extractUserInfo(authentication);
if (userInfo == null) {
log.error("사용자 정보 추출 실패");
handleOAuth2Error(response, new Exception("사용자 정보 추출 실패"));
return;
}
log.info("OAuth2 사용자 정보 추출 성공 - Provider: {}, Email: {}", userInfo.provider, userInfo.email);
// 현재 설정된 로그인 모드 확인
String currentLoginMode = loginModeService.getLoginMode();
log.info("현재 로그인 모드: {}", currentLoginMode);
// 통합 로그인 서비스를 통한 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);
// ★★★ 중요: 메인 페이지로 리다이렉트 (login.page가 아닌 /)
String redirectUrl = String.format("%s/?oauth_success=true&loginMode=%s", frontUrl, currentLoginMode);
log.info("OAuth2 성공 리다이렉트: {}", redirectUrl);
getRedirectStrategy().sendRedirect(request, response, redirectUrl);
} catch (Exception e) {
log.error("OAuth2 로그인 처리 중 오류", e);
handleOAuth2Error(response, e);
}
}
/**
* OAuth2User에서 사용자 정보 추출 (개선된 버전)
*/
private OAuth2UserInfo extractUserInfo(Authentication authentication) {
Object principal = authentication.getPrincipal();
String provider = determineProvider(authentication);
log.info("Principal 타입: {}, Provider: {}", principal.getClass().getSimpleName(), provider);
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, provider);
} else if (principal instanceof OAuth2User) {
// 일반 OAuth2 사용자
OAuth2User oauth2User = (OAuth2User) principal;
return extractOAuth2UserInfo(oauth2User, provider);
} else {
log.error("지원하지 않는 Principal 타입: {}", principal.getClass());
return null;
}
} catch (Exception e) {
log.error("사용자 정보 추출 중 오류", e);
return null;
}
}
/**
* OIDC 사용자 정보 추출 (구글)
*/
private OAuth2UserInfo extractOidcUserInfo(OidcUser oidcUser, String provider) {
try {
log.info("OIDC 사용자 정보 추출 시작 - Provider: {}", provider);
String id = oidcUser.getSubject(); // OIDC의 subject가 사용자 ID
String name = oidcUser.getFullName();
if (name == null || name.trim().isEmpty()) {
name = oidcUser.getGivenName();
}
if (name == null || name.trim().isEmpty()) {
name = oidcUser.getEmail();
}
String email = oidcUser.getEmail();
log.info("OIDC 정보 - ID: {}, Name: {}, Email: {}", id, name, email);
return new OAuth2UserInfo(provider, id, name, email);
} catch (Exception e) {
log.error("OIDC 사용자 정보 추출 실패", e);
return null;
}
}
/**
* 일반 OAuth2 사용자 정보 추출
*/
private OAuth2UserInfo extractOAuth2UserInfo(OAuth2User oauth2User, String provider) {
try {
log.info("OAuth2 사용자 정보 추출 시작 - Provider: {}", provider);
Map<String, Object> attributes = oauth2User.getAttributes();
log.info("OAuth2 Attributes: {}", attributes.keySet());
String id = null;
String name = null;
String email = null;
// 제공자별 정보 추출
switch (provider.toLowerCase()) {
case "kakao":
id = String.valueOf(attributes.get("id"));
Map<String, Object> kakaoAccount = (Map<String, Object>) attributes.get("kakao_account");
if (kakaoAccount != null) {
email = (String) kakaoAccount.get("email");
Map<String, Object> profile = (Map<String, Object>) kakaoAccount.get("profile");
if (profile != null) {
name = (String) profile.get("nickname");
}
}
break;
case "naver":
Map<String, Object> naverResponse = (Map<String, Object>) 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");
if (id == null) id = (String) attributes.get("id");
name = (String) attributes.get("name");
email = (String) attributes.get("email");
break;
}
log.info("추출된 정보 - ID: {}, Name: {}, Email: {}", id, name, email);
return new OAuth2UserInfo(provider, id, name, email);
} catch (Exception e) {
log.error("OAuth2 사용자 정보 추출 실패", e);
return null;
}
}
/**
* 제공자 결정
*/
private String determineProvider(Authentication authentication) {
if (authentication instanceof OAuth2AuthenticationToken) {
OAuth2AuthenticationToken oauth2Token = (OAuth2AuthenticationToken) authentication;
String registrationId = oauth2Token.getAuthorizedClientRegistrationId();
log.info("OAuth2 Registration ID: {}", registrationId);
return registrationId.toLowerCase(); // 소문자로 통일
}
String name = authentication.getName();
log.info("Authentication Name: {}", name);
if (name != null) {
String lowerName = name.toLowerCase();
if (lowerName.contains("google")) return "google";
if (lowerName.contains("kakao")) return "kakao";
if (lowerName.contains("naver")) return "naver";
}
// 기본값
return "google";
}
/**
* 로그인 이력 저장 - OAuth2 전용
*/
private void saveLoginHistory(HttpServletRequest request, MberVO mber, String provider) {
try {
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.setCntnOperSysm(httpRequestUtil.getOS(userAgent));
loginHistory.setDvcNm(httpRequestUtil.getDevice(userAgent));
loginHistory.setBrwsrNm(httpRequestUtil.getBrowser(userAgent));
lgnHstryService.LgnHstrySave(loginHistory);
} catch (Exception e) {
log.error("로그인 이력 저장 실패", e);
}
}
/**
* OAuth2 오류 처리
*/
private void handleOAuth2Error(HttpServletResponse response, Exception e) throws IOException {
String message = URLEncoder.encode("OAuth 로그인에 실패했습니다.", "UTF-8");
String errorUrl = String.format("%s/?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;
}
}
}