
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.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.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;
/**
* @author takensoft
* @since 2025.05.22
* @modification
* since | author | description
* 2025.05.22 | takensoft | 최초 등록
* 2025.05.26 | takensoft | OAuth 리다이렉트 기능 추가
*
* OAuth2 관련 통합 컨트롤러
*/
@RestController
@RequiredArgsConstructor
@Slf4j
@RequestMapping(value = "/oauth2")
public class OAuth2Controller {
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 로그인 후 사용자 정보 조회
*/
@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);
}
// 응답 데이터 구성
HashMap<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);
}
}
/**
* 지원하는 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 저장이 완료되기 전의 임시 상태
HashMap<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 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());
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;
}
}