Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.clokey.exception.GlobalBaseErrorCode;
import org.clokey.response.BaseResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@RequiredArgsConstructor
public class OidcLoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {
Expand All @@ -24,15 +27,58 @@ public void onAuthenticationFailure(
AuthenticationException exception)
throws IOException {

String requestURI = request.getRequestURI();
String queryString = request.getQueryString();
String errorCode = null;
String errorDescription = null;

log.error(
"[OIDC Failure] 로그인 실패 핸들러 시작 - RequestURI: {}, QueryString: {}",
requestURI,
queryString);
log.error(
"[OIDC Failure] 예외 정보 - ExceptionType: {}, Message: {}",
exception.getClass().getSimpleName(),
exception.getMessage());

if (exception instanceof OAuth2AuthenticationException oauth2Exception) {
errorCode = oauth2Exception.getError().getErrorCode();
errorDescription = oauth2Exception.getError().getDescription();
log.error(
"[OIDC Failure] OAuth2 예외 상세 - ErrorCode: {}, Description: {}",
errorCode,
errorDescription);

if (oauth2Exception.getCause() != null) {
log.error(
"[OIDC Failure] 원인 예외 - CauseType: {}, CauseMessage: {}",
oauth2Exception.getCause().getClass().getSimpleName(),
oauth2Exception.getCause().getMessage(),
oauth2Exception.getCause());
}
}

// 요청 파라미터 로깅
request.getParameterMap()
.forEach(
(key, values) ->
log.error(
"[OIDC Failure] Request Parameter - {}: {}",
key,
String.join(", ", values)));

response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");

String errorMessage = errorDescription != null ? errorDescription : exception.getMessage();
BaseResponse<Void> failureResponse =
BaseResponse.onFailure(
GlobalBaseErrorCode.UNAUTHORIZED.getCode(), exception.getMessage(), null);
GlobalBaseErrorCode.UNAUTHORIZED.getCode(), errorMessage, null);

String json = objectMapper.writeValueAsString(failureResponse);
response.getWriter().write(json);

log.error("[OIDC Failure] 실패 응답 전송 완료 - ErrorCode: {}", errorCode);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,31 +34,52 @@ public void onAuthenticationSuccess(
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException {

CustomPrincipal principal = (CustomPrincipal) authentication.getPrincipal();
Member member = principal.getMember();
log.info("[OIDC Success] 로그인 성공 핸들러 시작 - RequestURI: {}", request.getRequestURI());

String accessToken =
jwtTokenService.createAccessToken(member.getId(), member.getMemberRole());
String refreshToken = jwtTokenService.createRefreshToken(member.getId());
try {
CustomPrincipal principal = (CustomPrincipal) authentication.getPrincipal();
Member member = principal.getMember();
log.info(
"[OIDC Success] 인증 정보 추출 완료 - MemberId: {}, Email: {}",
member.getId(),
member.getEmail());

TokenResponse tokenResponse = TokenResponse.of(accessToken, refreshToken);
BaseResponse<TokenResponse> jsonResponse =
BaseResponse.onSuccess(GlobalBaseSuccessCode.CREATED, tokenResponse);
String jsonData = objectMapper.writeValueAsString(jsonResponse);
log.info("[OIDC Success] JWT 토큰 생성 시작 - MemberId: {}", member.getId());
String accessToken =
jwtTokenService.createAccessToken(member.getId(), member.getMemberRole());
String refreshToken = jwtTokenService.createRefreshToken(member.getId());
log.info("[OIDC Success] JWT 토큰 생성 완료 - MemberId: {}", member.getId());

String redirectUrl =
UriComponentsBuilder.fromUriString(redirectScheme + "://oauth/callback")
.queryParam("accessToken", accessToken)
.queryParam("refreshToken", refreshToken)
.build()
.toUriString();
TokenResponse tokenResponse = TokenResponse.of(accessToken, refreshToken);
BaseResponse<TokenResponse> jsonResponse =
BaseResponse.onSuccess(GlobalBaseSuccessCode.CREATED, tokenResponse);
String jsonData = objectMapper.writeValueAsString(jsonResponse);

String html = buildHtmlPage(jsonData, redirectUrl);
String redirectUrl =
UriComponentsBuilder.fromUriString(redirectScheme + "://oauth/callback")
.queryParam("accessToken", accessToken)
.queryParam("refreshToken", refreshToken)
.build()
.toUriString();

response.setContentType("text/html; charset=UTF-8");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(html);
response.getWriter().flush();
log.info("[OIDC Success] 리다이렉트 URL 생성 완료 - RedirectUrl: {}", redirectUrl);

String html = buildHtmlPage(jsonData, redirectUrl);

response.setContentType("text/html; charset=UTF-8");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(html);
response.getWriter().flush();

log.info("[OIDC Success] 로그인 성공 처리 완료 - MemberId: {}", member.getId());
} catch (Exception e) {
log.error(
"[OIDC Success] 성공 핸들러 처리 중 오류 발생 - Error: {}, Message: {}",
e.getClass().getSimpleName(),
e.getMessage(),
e);
throw e;
}
}

// NOTE : 개발자들을 위해서 웹에서 보여주면서도 딥링크 리다이랙트를 동시에 보내줌.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -29,33 +31,85 @@ public class CustomOAuth2UserService extends OidcUserService {
@Override
@Transactional
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
OidcUser oidcUser = super.loadUser(userRequest);

String provider = userRequest.getClientRegistration().getRegistrationId();
String oauthId = oidcUser.getName();
String email = oidcUser.getAttribute("email");

OauthProvider oauthProvider = OauthProvider.valueOf(provider.toUpperCase());
OauthInfo oauthInfo = OauthInfo.createOauthInfo(oauthId, oauthProvider);

Member member =
memberRepository
.findByOauthInfo(oauthInfo)
.orElseGet(
() -> {
Member newMember =
Member.createMember(
email,
uniqueUtil.generateRandomNickname(),
oauthInfo);
memberRepository.save(newMember);
eventPublisher.publishEvent(
MeiliSearchSyncEvent.of(
MeiliSearchSyncEvent.EntityType.MEMBER,
newMember.getId()));
return newMember;
});

return new CustomPrincipal(member, oidcUser.getAttributes(), oidcUser.getIdToken());
String clientId = userRequest.getClientRegistration().getClientId();
String clientAuthenticationMethod =
userRequest.getClientRegistration().getClientAuthenticationMethod().getValue();

log.info(
"[OIDC] 사용자 로드 시작 - Provider: {}, ClientId: {}, AuthMethod: {}",
provider,
clientId,
clientAuthenticationMethod);

try {
log.info("[OIDC] Access Token 교환 시도 - Provider: {}", provider);
OidcUser oidcUser = super.loadUser(userRequest);
log.info("[OIDC] Access Token 교환 성공 - Provider: {}", provider);

String oauthId = oidcUser.getName();
String email = oidcUser.getAttribute("email");
log.info(
"[OIDC] 사용자 정보 추출 - Provider: {}, OAuthId: {}, Email: {}",
provider,
oauthId,
email);

OauthProvider oauthProvider = OauthProvider.valueOf(provider.toUpperCase());
OauthInfo oauthInfo = OauthInfo.createOauthInfo(oauthId, oauthProvider);

log.info("[OIDC] 회원 조회 시작 - Provider: {}, OAuthId: {}", provider, oauthId);
Member member =
memberRepository
.findByOauthInfo(oauthInfo)
.orElseGet(
() -> {
log.info(
"[OIDC] 신규 회원 생성 - Provider: {}, Email: {}",
provider,
email);
Member newMember =
Member.createMember(
email,
uniqueUtil.generateRandomNickname(),
oauthInfo);
memberRepository.save(newMember);
eventPublisher.publishEvent(
MeiliSearchSyncEvent.of(
MeiliSearchSyncEvent.EntityType.MEMBER,
newMember.getId()));
log.info(
"[OIDC] 신규 회원 생성 완료 - MemberId: {}",
newMember.getId());
return newMember;
});

log.info("[OIDC] 사용자 로드 완료 - Provider: {}, MemberId: {}", provider, member.getId());
return new CustomPrincipal(member, oidcUser.getAttributes(), oidcUser.getIdToken());
} catch (OAuth2AuthenticationException e) {
// OAuth2AuthenticationException은 그대로 전달 (이미 OAuth2Error 포함)
log.error(
"[OIDC] 사용자 로드 실패 - Provider: {}, ErrorCode: {}, Description: {}",
provider,
e.getError() != null ? e.getError().getErrorCode() : "UNKNOWN",
e.getError() != null ? e.getError().getDescription() : e.getMessage(),
e);
throw e;
} catch (Exception e) {
// 일반 Exception을 OAuth2AuthenticationException으로 변환
log.error(
"[OIDC] 예상치 못한 오류 발생 - Provider: {}, Error: {}, Message: {}",
provider,
e.getClass().getSimpleName(),
e.getMessage(),
e);

OAuth2Error oauth2Error =
new OAuth2Error(
OAuth2ErrorCodes.SERVER_ERROR,
"사용자 로드 중 오류 발생: " + e.getMessage(),
null);
throw new OAuth2AuthenticationException(oauth2Error, e);
}
}
}
1 change: 1 addition & 0 deletions clokey-api/src/main/resources/application-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ spring:
client-secret: ${APPLE_CLIENT_SECRET}
redirect-uri: ${APPLE_REDIRECT_URI}
authorization-grant-type: authorization_code
client-authentication-method: private_key_jwt
scope:
- openid
provider: apple
Expand Down
1 change: 1 addition & 0 deletions clokey-api/src/main/resources/application-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ spring:
client-secret: ${APPLE_CLIENT_SECRET}
redirect-uri: ${APPLE_REDIRECT_URI}
authorization-grant-type: authorization_code
client-authentication-method: private_key_jwt
scope:
- openid
provider: apple
Expand Down
1 change: 1 addition & 0 deletions clokey-api/src/main/resources/application-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ spring:
client-secret: ${APPLE_CLIENT_SECRET}
redirect-uri: ${APPLE_REDIRECT_URI}
authorization-grant-type: authorization_code
client-authentication-method: private_key_jwt
scope:
- openid
provider: apple
Expand Down