
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.loginPolicy.service.LoginModeService;
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.JWTUtil;
import com.takensoft.common.util.ResponseUtil;
import jakarta.servlet.http.Cookie;
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.*;
import java.io.IOException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
/**
* @author takensoft
* @since 2025.05.22
* @modification
* since | author | description
* 2025.05.22 | takensoft | 최초 등록
* 2025.05.26 | takensoft | OAuth 리다이렉트 기능 추가
* 2025.05.28 | 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;
private final LoginModeService loginModeService;
private final JWTUtil jwtUtil;
@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);
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;
response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
response.sendRedirect(redirectUrl);
} catch (IllegalArgumentException e) {
handleError(response, "invalid_provider", e.getMessage());
} catch (SecurityException e) {
handleError(response, "security_check_failed", e.getMessage());
} catch (Exception e) {
handleError(response, "system_error", "OAuth 로그인 중 오류가 발생했습니다.");
}
}
/**
* OAuth2 로그인 후 사용자 정보 조회
*/
@PostMapping(value = "/getUserInfo.json")
public ResponseEntity<?> getUserInfo(HttpServletRequest request) {
try {
// 로그인 모드 확인
String loginMode = loginModeService.getLoginMode();
// 현재 로그인한 사용자 ID 조회
String currentUserId = null;
MberVO mberInfo = null;
if ("S".equals(loginMode)) {
// 세션 모드 - 세션에서 직접 조회
HttpSession session = request.getSession(false);
if (session != null) {
currentUserId = (String) session.getAttribute("mbrId");
if (currentUserId != null) {
// 세션에 저장된 정보로 응답 생성
HashMap<String, Object> result = new HashMap<>();
result.put("mbrId", session.getAttribute("mbrId"));
result.put("mbrNm", session.getAttribute("mbrNm"));
result.put("roles", session.getAttribute("roles"));
return resUtil.successRes(result, MessageCode.COMMON_SUCCESS);
}
} else {
return resUtil.errorRes(MessageCode.LOGIN_USER_NOT_FOUND);
}
} else {
// JWT 모드 - 토큰에서 조회
String authHeader = request.getHeader("Authorization");
System.out.println("Authorization Header: " + authHeader);
String token = null;
if (authHeader != null && !authHeader.isEmpty()) {
// Authorization 헤더에서 토큰 추출
token = jwtUtil.extractToken(authHeader);
} else {
// Authorization 헤더가 없으면 OAuth 전용 쿠키에서 확인
token = getTokenFromOAuthCookie(request);
}
if (token == null || token.isEmpty()) {
return resUtil.errorRes(MessageCode.LOGIN_USER_NOT_FOUND);
}
try {
currentUserId = (String) jwtUtil.getClaim(token, "mbrId");
// JWT 모드에서는 DB에서 최신 정보 조회
HashMap<String, Object> params = new HashMap<>();
params.put("mbrId", currentUserId);
mberInfo = mberService.findByMbr(params);
if (mberInfo == null) {
return resUtil.errorRes(MessageCode.LOGIN_USER_NOT_FOUND);
}
// 응답 데이터 구성
HashMap<String, Object> result = new HashMap<>();
result.put("mbrId", mberInfo.getMbrId());
result.put("mbrNm", mberInfo.getMbrNm());
result.put("roles", mberInfo.getAuthorList());
// 토큰도 함께 전달
if (authHeader != null && !authHeader.isEmpty()) {
result.put("token", authHeader);
} else {
// 쿠키에서 가져온 토큰이면 Bearer 형태로 반환
String oauthToken = getTokenFromOAuthCookie(request);
if (oauthToken != null) {
result.put("token", "Bearer " + oauthToken);
}
}
return resUtil.successRes(result, MessageCode.COMMON_SUCCESS);
} catch (IllegalArgumentException e) {
return resUtil.errorRes(MessageCode.LOGIN_USER_NOT_FOUND);
}
}
// 여기까지 왔다면 사용자를 찾을 수 없음
return resUtil.errorRes(MessageCode.LOGIN_USER_NOT_FOUND);
} catch (Exception e) {
return resUtil.errorRes(MessageCode.COMMON_UNKNOWN_ERROR);
}
}
/**
* OAuth 전용 쿠키에서 access token 추출
*/
private String getTokenFromOAuthCookie(HttpServletRequest request) {
if (request.getCookies() != null) {
for (Cookie cookie : request.getCookies()) {
if ("oauth_access_token".equals(cookie.getName())) {
return cookie.getValue();
}
}
}
return null;
}
/**
* 지원하는 OAuth 제공자 목록 조회 API
*/
@GetMapping("/providers")
public ResponseEntity<?> getSupportedProviders() {
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);
}
}
/**
* 보안 검증 (필요시 확장)
*/
private void validateSecurity(HttpServletRequest request) {
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.page?error=%s&message=%s", FRONT_URL, errorCode, encodedMessage);
response.sendRedirect(errorUrl);
}
}