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 @@ -82,7 +82,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.tokenEndpoint(token -> token.accessTokenResponseClient(accessTokenResponseClient())) // 토큰 엔드포인트 설정
.successHandler(oAuth2LoginSuccessHandler) // 동의하고 계속하기를 눌렀을 때 Handler 설정
.failureHandler(oAuth2LoginFailureHandler) // 소셜 로그인 실패 시 핸들러 설정

.userInfoEndpoint(userInfo -> userInfo
.userService(customOAuth2UserService)))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,17 @@ public class JwtAuthenticationProcessingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {

String requestURI = request.getRequestURI();
log.info("JWT 인증 필터 처리 - URI: {}, Method: {}", requestURI, request.getMethod());

// OAuth2 로그인 콜백 URL 확인
if (requestURI.contains("/oauth2/") || requestURI.contains("/login/oauth2/")) {
log.info("OAuth2 콜백 URL 감지: {}", requestURI);
}

if (request.getRequestURI().equals(NO_CHECK_URL)) {
log.info("로그인 URL - JWT 필터 건너뛰기");
filterChain.doFilter(request, response); // "/login" 요청이 들어오면, 다음 필터 호출
return; // return으로 이후 현재 필터 진행 막기 (안해주면 아래로 내려가서 계속 필터 진행시킴)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ public class TokenSendManager {

public void addTokenCookie(HttpServletResponse response, String name, String value, String path, int maxAge,
boolean httpOnly) {
log.info("토큰 쿠키 추가 - 이름: {}, Path: {}, MaxAge: {}, HttpOnly: {}", name, path, maxAge, httpOnly);

Cookie cookie = new Cookie(name, value);
cookie.setHttpOnly(httpOnly);
cookie.setPath(path);
cookie.setMaxAge(maxAge);
response.addCookie(cookie);

log.info("토큰 쿠키 응답에 추가 완료");
}

// public ResponseEntity<UserLoginSuccessRes> addTokenResponseBody(String accessToken, String refreshToken,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,27 @@ public class OAuth2LoginFailureHandler implements AuthenticationFailureHandler {
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException,
ServletException {

log.error("=== OAuth2 로그인 실패 핸들러 시작 ===");
log.error("요청 URI: {}", request.getRequestURI());
log.error("요청 파라미터: {}", request.getQueryString());
log.error("User-Agent: {}", request.getHeader("User-Agent"));
log.error("Referer: {}", request.getHeader("Referer"));

log.error("인증 예외 타입: {}", exception.getClass().getSimpleName());
log.error("인증 예외 메시지: {}", exception.getMessage());
log.error("인증 예외 스택트레이스: ", exception);

// 원인별 상세 정보 추가
if (exception.getCause() != null) {
log.error("인증 예외 원인: {}", exception.getCause().getMessage());
log.error("인증 예외 원인 스택트레이스: ", exception.getCause());
}

response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().write("소셜 로그인 실패! 서버 로그를 확인해주세요.");
log.info("소셜 로그인에 실패했습니다. 에러 메시지 : {}", exception.getMessage());

log.error("=== OAuth2 로그인 실패 핸들러 완료 ===");
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -35,37 +35,60 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo
Authentication authentication) throws
IOException, ServletException {

log.info("=== OAuth2 로그인 성공 핸들러 시작 ===");
log.info("요청 URI: {}", request.getRequestURI());
log.info("인증 객체: {}", authentication.getClass().getSimpleName());

try {
CustomOAuth2User oAuth2User = (CustomOAuth2User)authentication.getPrincipal();
log.info("OAuth2 사용자 정보 - 이메일: {}, 이름: {}",
oAuth2User.getEmail(), oAuth2User.getName());

// TODO : 아래 줄 예외 테스트 진행 필요
UserInfo userInfo = userServiceFacade.getUserInfoByEmail(
oAuth2User.getEmail()); // 사용자 정보가 없으면 예외 발생 (회원가입 페이지로 리다이렉트하기 위함

log.info("DB에서 조회된 사용자 정보 - ID: {}, 역할: {}", userInfo.getId(), userInfo.getRole());

loginSuccess(response, oAuth2User, userInfo.getRole()); // 로그인에 성공한 경우 access, refresh 토큰 생성

// User의 Role이 GUEST일 경우 처음 요청한 회원이므로 회원가입 페이지로 리다이렉트
if (userInfo.getRole() == Role.GUEST) {

log.info("GUEST 사용자 - 회원가입 페이지로 리다이렉트: {}", jwtConfig.getGuestFrontendUrl());
response.sendRedirect(jwtConfig.getGuestFrontendUrl()); // 프론트의 회원가입 추가 정보 입력 폼으로 리다이렉트

} else {

log.info("일반 사용자 - 메인 페이지로 리다이렉트: {}", jwtConfig.getUserFrontendUrl());
response.sendRedirect(jwtConfig.getUserFrontendUrl());
}

log.info("=== OAuth2 로그인 성공 핸들러 완료 ===");

} catch (Exception e) {
log.error("=== OAuth2 로그인 성공 핸들러 실패 ===");
log.error("에러 메시지: {}", e.getMessage());
log.error("에러 스택트레이스: ", e);
throw e;
}

}

// TODO : 소셜 로그인 시에도 무조건 토큰 생성하지 말고 JWT 인증 필터처럼 RefreshToken 유/무에 따라 다르게 처리해보기
private void loginSuccess(HttpServletResponse response, CustomOAuth2User oAuth2User, Role role) throws IOException {
log.info("토큰 생성 시작 - 사용자: {}, 역할: {}", oAuth2User.getEmail(), role);

String accessToken = jwtService.createAccessToken(oAuth2User.getEmail());
String refreshToken = jwtService.createRefreshToken();

log.info("토큰 생성 완료 - AccessToken 길이: {}, RefreshToken 길이: {}",
accessToken.length(), refreshToken.length());

jwtService.sendAccessAndRefreshToken(response, accessToken, refreshToken, role);
log.info("응답 헤더에 토큰 전송 완료");

// RefreshTokenRepository를 직접 사용하지 말고 반드시 RefreshTokenService를 통해서만 접근하세요. (key-value 방식으로 변경됨)
refreshTokenServiceImpl.saveRefreshTokenInRedis(oAuth2User.getEmail(), refreshToken);
log.info("RefreshToken Redis 저장 완료");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,21 @@ public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequ

@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
log.info("=== CustomOAuth2UserService.loadUser 시작 ===");

String registrationId = userRequest.getClientRegistration().getRegistrationId();
log.info("OAuth2 Provider: {}", registrationId);
log.info("Client Registration ID: {}", userRequest.getClientRegistration().getClientId());
log.info("Client Registration URI: {}", userRequest.getClientRegistration().getRedirectUri());

OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
SocialType socialType = getSocialType(registrationId);
log.info("변환된 SocialType: {}", socialType);

String userNameAttributeName = userRequest.getClientRegistration()
.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName(); // OAuth2 로그인 시 키(PK)가 되는값
log.info("UserName Attribute Name: {}", userNameAttributeName);

Map<String, Object> attributes;
/**
* DefaultOAuth2UserService 객체를 생성하여, loadUser(userRequest)를 통해 DefaultOAuth2User 객체를 생성 후 반환
Expand All @@ -49,20 +59,24 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic
* 결과적으로, OAuth2User는 OAuth 서비스에서 가져온 유저 정보를 담고 있는 유저
*/
if (registrationId.contains(APPLE_NAME)) {
log.info("Apple 로그인 처리 시작");
// Apple 로그인의 경우 JWT 토큰에서 사용자 정보를 디코드
String idToken = userRequest.getAdditionalParameters().get("id_token").toString();
log.info("Apple ID Token 길이: {}", idToken.length());

attributes = decodeJwtTokenPayload(idToken);
attributes.put("id_token", idToken);

System.out.println("attributes = " + attributes);
System.out.println("userNameAttributeName = " + userNameAttributeName);
log.info("Apple 사용자 정보 디코드 완료 - attributes: {}", attributes);
log.info("Apple userNameAttributeName: {}", userNameAttributeName);

// socialType에 따라 유저 정보를 통해 OAuthAttributes 객체 생성
OAuthAttributes extractAttributes = OAuthAttributes.of(socialType, userNameAttributeName, attributes);

System.out.println("extractAttributes = " + extractAttributes);
log.info("Apple extractAttributes: {}", extractAttributes);
UserInfo createdUser = getUser(extractAttributes, socialType); // getUser() 메소드로 User 객체 생성 후 반환
System.out.println("createdUser = " + createdUser);
log.info("Apple 사용자 생성/조회 완료 - ID: {}, 이메일: {}", createdUser.getId(), createdUser.getEmail());

// CustomOAuth2User 객체 생성
return new CustomOAuth2User(
Collections.singleton(new SimpleGrantedAuthority(createdUser.getRole().getKey())),
Expand All @@ -72,6 +86,7 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic
createdUser.getRole()
);
} else {
log.info("Google/Kakao 로그인 처리 시작");
OAuth2User oAuth2User = delegate.loadUser(userRequest);

/**
Expand All @@ -81,20 +96,27 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic
*/

attributes = oAuth2User.getAttributes(); // 소셜 로그인에서 API가 제공하는 userInfo의 Json 값(유저 정보들)
log.info("OAuth2 사용자 정보 조회 완료 - attributes: {}", attributes);

// socialType에 따라 유저 정보를 통해 OAuthAttributes 객체 생성
OAuthAttributes extractAttributes = OAuthAttributes.of(socialType, userNameAttributeName, attributes);
log.info("OAuthAttributes 생성 완료: {}", extractAttributes);

UserInfo createdUser = getUser(extractAttributes, socialType); // getUser() 메소드로 User 객체 생성 후 반환
log.info("사용자 생성/조회 완료 - ID: {}, 이메일: {}, 역할: {}",
createdUser.getId(), createdUser.getEmail(), createdUser.getRole());

// DefaultOAuth2User를 구현한 CustomOAuth2User 객체를 생성해서 반환
return new CustomOAuth2User(
CustomOAuth2User customUser = new CustomOAuth2User(
Collections.singleton(new SimpleGrantedAuthority(createdUser.getRole().getKey())),
attributes,
extractAttributes.getNameAttributeKey(),
createdUser.getEmail(),
createdUser.getRole()
);

log.info("=== CustomOAuth2UserService.loadUser 완료 ===");
return customUser;
}
}

Expand All @@ -113,15 +135,21 @@ private SocialType getSocialType(String registrationId) {
* 만약 찾은 회원이 있다면, 그대로 반환하고 없다면 saveUser()를 호출하여 회원을 저장한다.
*/
private UserInfo getUser(OAuthAttributes attributes, SocialType socialType) {
log.info("사용자 조회/생성 시작 - SocialType: {}, SocialId: {}",
socialType, attributes.getOauth2UserInfo().getId());

UserInfo findUser = userRepository.findBySocialTypeAndSocialId(socialType,
attributes.getOauth2UserInfo().getId()).orElse(null);

if (findUser == null) {
log.info("신규 사용자 - 회원가입 진행");
return saveUser(attributes, socialType);
} else if (findUser.isStatus()) {
log.info("기존 사용자 상태 업데이트 - ID: {}", findUser.getId());
findUser.updateStatus(false);
userRepository.save(findUser);
} else {
log.info("기존 사용자 로그인 - ID: {}, 이메일: {}", findUser.getId(), findUser.getEmail());
}
return findUser;
}
Expand All @@ -131,9 +159,16 @@ private UserInfo getUser(OAuthAttributes attributes, SocialType socialType) {
* 생성된 User 객체를 DB에 저장 : socialType, socialId, email, role 값만 있는 상태
*/
private UserInfo saveUser(OAuthAttributes attributes, SocialType socialType) {
log.info("신규 사용자 저장 시작 - 이메일: {}, SocialType: {}",
attributes.getOauth2UserInfo().getEmail(), socialType);

UserInfo createdUser = attributes.toEntity(socialType, attributes.getOauth2UserInfo());
return userRepository.save(createdUser);
UserInfo savedUser = userRepository.save(createdUser);

log.info("신규 사용자 저장 완료 - ID: {}, 이메일: {}, 역할: {}",
savedUser.getId(), savedUser.getEmail(), savedUser.getRole());

return savedUser;
}

private Map<String, Object> decodeJwtTokenPayload(String jwtToken) {
Expand Down
Loading