From e0cada5aac741d1f38ef65c6907c523282225cff Mon Sep 17 00:00:00 2001 From: 1223v <1223v@naver.com> Date: Wed, 30 Jul 2025 23:40:42 +0900 Subject: [PATCH] =?UTF-8?q?Fix:=20=EC=86=8C=EC=85=9C=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EB=B0=B0=ED=8F=AC=20=ED=99=98=EA=B2=BD=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/SpringSecurityConfig.java | 2 +- .../JwtAuthenticationProcessingFilter.java | 10 ++++ .../service/sendmanger/TokenSendManager.java | 3 ++ .../handler/OAuth2LoginFailureHandler.java | 20 +++++++- .../handler/OAuth2LoginSuccessHandler.java | 27 ++++++++++- .../service/CustomOAuth2UserService.java | 47 ++++++++++++++++--- 6 files changed, 99 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/readyvery/readyverydemo/config/SpringSecurityConfig.java b/src/main/java/com/readyvery/readyverydemo/config/SpringSecurityConfig.java index 30c01c6..44c84fd 100644 --- a/src/main/java/com/readyvery/readyverydemo/config/SpringSecurityConfig.java +++ b/src/main/java/com/readyvery/readyverydemo/config/SpringSecurityConfig.java @@ -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))) diff --git a/src/main/java/com/readyvery/readyverydemo/security/jwt/filter/JwtAuthenticationProcessingFilter.java b/src/main/java/com/readyvery/readyverydemo/security/jwt/filter/JwtAuthenticationProcessingFilter.java index 7adb34a..1564a6a 100644 --- a/src/main/java/com/readyvery/readyverydemo/security/jwt/filter/JwtAuthenticationProcessingFilter.java +++ b/src/main/java/com/readyvery/readyverydemo/security/jwt/filter/JwtAuthenticationProcessingFilter.java @@ -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으로 이후 현재 필터 진행 막기 (안해주면 아래로 내려가서 계속 필터 진행시킴) } diff --git a/src/main/java/com/readyvery/readyverydemo/security/jwt/service/sendmanger/TokenSendManager.java b/src/main/java/com/readyvery/readyverydemo/security/jwt/service/sendmanger/TokenSendManager.java index d42483f..f31ddad 100644 --- a/src/main/java/com/readyvery/readyverydemo/security/jwt/service/sendmanger/TokenSendManager.java +++ b/src/main/java/com/readyvery/readyverydemo/security/jwt/service/sendmanger/TokenSendManager.java @@ -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 addTokenResponseBody(String accessToken, String refreshToken, diff --git a/src/main/java/com/readyvery/readyverydemo/security/oauth2/handler/OAuth2LoginFailureHandler.java b/src/main/java/com/readyvery/readyverydemo/security/oauth2/handler/OAuth2LoginFailureHandler.java index dd747ff..66409ed 100644 --- a/src/main/java/com/readyvery/readyverydemo/security/oauth2/handler/OAuth2LoginFailureHandler.java +++ b/src/main/java/com/readyvery/readyverydemo/security/oauth2/handler/OAuth2LoginFailureHandler.java @@ -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 로그인 실패 핸들러 완료 ==="); } } diff --git a/src/main/java/com/readyvery/readyverydemo/security/oauth2/handler/OAuth2LoginSuccessHandler.java b/src/main/java/com/readyvery/readyverydemo/security/oauth2/handler/OAuth2LoginSuccessHandler.java index 9e2b915..e8293e4 100644 --- a/src/main/java/com/readyvery/readyverydemo/security/oauth2/handler/OAuth2LoginSuccessHandler.java +++ b/src/main/java/com/readyvery/readyverydemo/security/oauth2/handler/OAuth2LoginSuccessHandler.java @@ -35,24 +35,39 @@ 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; } @@ -60,12 +75,20 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo // 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 저장 완료"); } } diff --git a/src/main/java/com/readyvery/readyverydemo/security/oauth2/service/CustomOAuth2UserService.java b/src/main/java/com/readyvery/readyverydemo/security/oauth2/service/CustomOAuth2UserService.java index ba7a2ae..4176831 100644 --- a/src/main/java/com/readyvery/readyverydemo/security/oauth2/service/CustomOAuth2UserService.java +++ b/src/main/java/com/readyvery/readyverydemo/security/oauth2/service/CustomOAuth2UserService.java @@ -36,11 +36,21 @@ public class CustomOAuth2UserService implements OAuth2UserService 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 attributes; /** * DefaultOAuth2UserService 객체를 생성하여, loadUser(userRequest)를 통해 DefaultOAuth2User 객체를 생성 후 반환 @@ -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())), @@ -72,6 +86,7 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic createdUser.getRole() ); } else { + log.info("Google/Kakao 로그인 처리 시작"); OAuth2User oAuth2User = delegate.loadUser(userRequest); /** @@ -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; } } @@ -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; } @@ -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 decodeJwtTokenPayload(String jwtToken) {