diff --git a/src/main/java/com/example/mody/domain/auth/handler/OAuth2SuccessHandler.java b/src/main/java/com/example/mody/domain/auth/handler/OAuth2SuccessHandler.java index 9909a8d5..1d419882 100644 --- a/src/main/java/com/example/mody/domain/auth/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/example/mody/domain/auth/handler/OAuth2SuccessHandler.java @@ -4,6 +4,7 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import jakarta.servlet.http.Cookie; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; @@ -62,7 +63,19 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo // boolean isNewMember = member.getCreatedAt().equals(member.getUpdatedAt()); // Access Token, Refresh Token 발급 - String newAccessToken = authCommandService.processLoginSuccess(member, response); + String newRefreshToken = authCommandService.processLoginKakaoSuccess(member, response); + + // 쿠키에 저장 + // Refresh Token을 HTTP-Only, Secure 쿠키로 설정 + Cookie refreshTokenCookie = new Cookie("refresh_token", newRefreshToken); + refreshTokenCookie.setHttpOnly(true); // JavaScript 접근 차단 + refreshTokenCookie.setSecure(true); // HTTPS에서만 전송 (로컬 개발 시 false 가능) + refreshTokenCookie.setPath("/"); // 모든 경로에서 사용 가능 + refreshTokenCookie.setMaxAge(7 * 24 * 60 * 60); // 7일 유효기간 설정 + + // SameSite 설정을 위한 헤더 추가 + response.setHeader("Set-Cookie", "refresh_token=" + newRefreshToken + + "; Path=/; HttpOnly; Secure; SameSite=None; Max-Age=" + (7 * 24 * 60 * 60)); String tempUrl = (!member.isRegistrationCompleted()) ? FRONT_SIGNUP_URL : FRONT_HOME_URL; diff --git a/src/main/java/com/example/mody/domain/auth/jwt/JwtAuthenticationFilter.java b/src/main/java/com/example/mody/domain/auth/jwt/JwtAuthenticationFilter.java index ea607add..46da073e 100644 --- a/src/main/java/com/example/mody/domain/auth/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/com/example/mody/domain/auth/jwt/JwtAuthenticationFilter.java @@ -46,13 +46,16 @@ protected boolean shouldNotFilter(HttpServletRequest request) throws ServletExce uri = uri.substring(contextPath.length()); } log.info("JwtAuthenticationFilter - Request URI after context removal: {}", uri); - boolean skip = uri.startsWith("/auth/") - && !uri.startsWith("/auth/signup/complete") - && !uri.startsWith("/auth/logout")|| + boolean skip = uri.startsWith("/auth/reissue") || uri.startsWith("/oauth2/") || uri.startsWith("/email/") || uri.startsWith("/swagger-ui/") || uri.startsWith("/v3/api-docs/"); + + skip = (!uri.startsWith("/auth/signup/complete") + && !uri.startsWith("/auth/logout")) && skip; + + log.info("JwtAuthenticationFilter - shouldNotFilter returns: {}", skip); return skip; diff --git a/src/main/java/com/example/mody/domain/auth/service/AuthCommandService.java b/src/main/java/com/example/mody/domain/auth/service/AuthCommandService.java index 40aea7f2..4115a6c4 100644 --- a/src/main/java/com/example/mody/domain/auth/service/AuthCommandService.java +++ b/src/main/java/com/example/mody/domain/auth/service/AuthCommandService.java @@ -14,4 +14,6 @@ public interface AuthCommandService { void logout(String refreshToken); String processLoginSuccess(Member member, HttpServletResponse response); + + String processLoginKakaoSuccess(Member member, HttpServletResponse response); } diff --git a/src/main/java/com/example/mody/domain/auth/service/AuthCommandServiceImpl.java b/src/main/java/com/example/mody/domain/auth/service/AuthCommandServiceImpl.java index 4b6455d0..14db2ca8 100644 --- a/src/main/java/com/example/mody/domain/auth/service/AuthCommandServiceImpl.java +++ b/src/main/java/com/example/mody/domain/auth/service/AuthCommandServiceImpl.java @@ -108,4 +108,17 @@ public String processLoginSuccess(Member member, HttpServletResponse response) { return newAccessToken; } + + // 카카오 전용, 리프레시 토큰 발급 + @Override + public String processLoginKakaoSuccess(Member member, HttpServletResponse response) { + // Access Token, Refresh Token 발급 + String newAccessToken = jwtProvider.createAccessToken(member.getId().toString()); + String newRefreshToken = jwtProvider.createRefreshToken(member.getId().toString()); + + // Refresh Token 저장 + saveRefreshToken(member, newRefreshToken); + + return newRefreshToken; + } } diff --git a/src/main/java/com/example/mody/domain/bodytype/repository/MemberBodyTypeRepository.java b/src/main/java/com/example/mody/domain/bodytype/repository/MemberBodyTypeRepository.java index fc398cab..7a9472b8 100644 --- a/src/main/java/com/example/mody/domain/bodytype/repository/MemberBodyTypeRepository.java +++ b/src/main/java/com/example/mody/domain/bodytype/repository/MemberBodyTypeRepository.java @@ -9,7 +9,6 @@ @Repository public interface MemberBodyTypeRepository extends JpaRepository { - Optional findTopByMemberOrderByCreatedAt(Member member); Long countAllByMember(Member member); Optional findTopByMemberOrderByCreatedAtDesc(Member member); } diff --git a/src/main/java/com/example/mody/domain/bodytype/service/BodyTypeService.java b/src/main/java/com/example/mody/domain/bodytype/service/BodyTypeService.java index 3ded547d..151b6626 100644 --- a/src/main/java/com/example/mody/domain/bodytype/service/BodyTypeService.java +++ b/src/main/java/com/example/mody/domain/bodytype/service/BodyTypeService.java @@ -22,7 +22,7 @@ public class BodyTypeService { * @return 마지막 체형 분석이 존재하지 않을 경우 empty Optional을 반환함. */ public Optional findLastBodyType(Member member){ - Optional optionalMemberBodyType = memberBodyTypeRepository.findTopByMemberOrderByCreatedAt(member); + Optional optionalMemberBodyType = memberBodyTypeRepository.findTopByMemberOrderByCreatedAtDesc(member); return optionalMemberBodyType.map(MemberBodyType::getBodyType); } } diff --git a/src/main/java/com/example/mody/domain/member/entity/Member.java b/src/main/java/com/example/mody/domain/member/entity/Member.java index 175a88c0..9d1a44cd 100644 --- a/src/main/java/com/example/mody/domain/member/entity/Member.java +++ b/src/main/java/com/example/mody/domain/member/entity/Member.java @@ -113,8 +113,8 @@ public class Member extends BaseEntity { @Enumerated(EnumType.STRING) private LoginType loginType; - @Builder.Default - private boolean isRegistrationCompleted = false; // 회원가입 완료 여부 + @Column(columnDefinition = "boolean default false") + private boolean isRegistrationCompleted; // 회원가입 완료 여부 public void completeRegistration(String nickname, LocalDate birthDate, Gender gender, Integer height , String profileImageUrl) { diff --git a/src/main/java/com/example/mody/domain/member/service/MemberCommandServiceImpl.java b/src/main/java/com/example/mody/domain/member/service/MemberCommandServiceImpl.java index e646218e..1f420751 100644 --- a/src/main/java/com/example/mody/domain/member/service/MemberCommandServiceImpl.java +++ b/src/main/java/com/example/mody/domain/member/service/MemberCommandServiceImpl.java @@ -1,5 +1,7 @@ package com.example.mody.domain.member.service; +import jakarta.persistence.EntityManager; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -27,11 +29,12 @@ public class MemberCommandServiceImpl implements MemberCommandService { private final MemberRepository memberRepository; private final PasswordEncoder passwordEncoder; private final AuthCommandService authCommandService; + private final MemberQueryService memberQueryService; @Override public void completeRegistration(Member member, MemberRegistrationRequest request) { - - member.completeRegistration( + Member unregisteredMember = memberQueryService.findMemberById(member.getId()); // 영속성 컨텍스트가 관리하도록 + unregisteredMember.completeRegistration( request.getNickname(), request.getBirthDate(), request.getGender(),