
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.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<String, Object> 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<String, Object> attributes = oauth2User.getAttributes();
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");
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;
}
}
}