From c3813c0e5534d92bb3b4d8efa14e20748b9524eb Mon Sep 17 00:00:00 2001 From: dl-00-e8 Date: Mon, 29 Sep 2025 00:36:28 +0900 Subject: [PATCH 01/10] =?UTF-8?q?chore:=20BaseTimeEntity=EB=A5=BC=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=9C=20App?= =?UTF-8?q?lication=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EB=B0=8F=20=ED=99=98=EA=B2=BD=EC=84=A4=EC=A0=95=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apimodule/ApiModuleApplication.java | 2 -- .../domainmodule/common/BaseTimeEntity.java | 6 +++--- .../triple/domainmodule/config/JpaConfig.java | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 domain-module/src/main/java/hongik/triple/domainmodule/config/JpaConfig.java diff --git a/api-module/src/main/java/hongik/triple/apimodule/ApiModuleApplication.java b/api-module/src/main/java/hongik/triple/apimodule/ApiModuleApplication.java index 9bafdb1..007f79a 100644 --- a/api-module/src/main/java/hongik/triple/apimodule/ApiModuleApplication.java +++ b/api-module/src/main/java/hongik/triple/apimodule/ApiModuleApplication.java @@ -16,8 +16,6 @@ AcneLogInfraRoot.class, ApiModuleApplication.class }) -@EntityScan(basePackages = "hongik.triple.domainmodule") // 도메인 모듈의 엔티티 경로 -@EnableJpaRepositories(basePackages = "hongik.triple.domainmodule") // 도메인 모듈의 레포지토리 경로 public class ApiModuleApplication { public static void main(String[] args) { diff --git a/domain-module/src/main/java/hongik/triple/domainmodule/common/BaseTimeEntity.java b/domain-module/src/main/java/hongik/triple/domainmodule/common/BaseTimeEntity.java index 82c9a08..55b9a79 100644 --- a/domain-module/src/main/java/hongik/triple/domainmodule/common/BaseTimeEntity.java +++ b/domain-module/src/main/java/hongik/triple/domainmodule/common/BaseTimeEntity.java @@ -16,13 +16,13 @@ public class BaseTimeEntity { @CreatedDate - @Column(name = "createdAt", updatable = false, nullable = false) + @Column(name = "created_at", updatable = false, nullable = false) private LocalDateTime createdAt; @LastModifiedDate - @Column(name = "modifiedAt", nullable = false) + @Column(name = "modified_at", nullable = false) private LocalDateTime modifiedAt; - @Column(name = "deletedAt") + @Column(name = "deleted_at") private LocalDateTime deletedAt; } diff --git a/domain-module/src/main/java/hongik/triple/domainmodule/config/JpaConfig.java b/domain-module/src/main/java/hongik/triple/domainmodule/config/JpaConfig.java new file mode 100644 index 0000000..e1fff3e --- /dev/null +++ b/domain-module/src/main/java/hongik/triple/domainmodule/config/JpaConfig.java @@ -0,0 +1,19 @@ +package hongik.triple.domainmodule.config; + +import hongik.triple.domainmodule.AcneLogDomainRoot; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +@EnableJpaAuditing +@EnableJpaRepositories(basePackageClasses = {AcneLogDomainRoot.class}) +@EntityScan(basePackageClasses = {AcneLogDomainRoot.class}) +@Configuration +public class JpaConfig { + + @PersistenceContext + private EntityManager em; +} \ No newline at end of file From a3e1ab901abe3105b12c6f537f0f9d65932c12b8 Mon Sep 17 00:00:00 2001 From: dl-00-e8 Date: Mon, 29 Sep 2025 00:36:39 +0900 Subject: [PATCH 02/10] =?UTF-8?q?chore:=20Test=EB=A5=BC=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20Security=20=EC=84=A4=EC=A0=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hongik/triple/apimodule/global/config/SecurityConfig.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/api-module/src/main/java/hongik/triple/apimodule/global/config/SecurityConfig.java b/api-module/src/main/java/hongik/triple/apimodule/global/config/SecurityConfig.java index b24cc14..fd25aa3 100644 --- a/api-module/src/main/java/hongik/triple/apimodule/global/config/SecurityConfig.java +++ b/api-module/src/main/java/hongik/triple/apimodule/global/config/SecurityConfig.java @@ -67,8 +67,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers("/error").permitAll() .requestMatchers("/api/v1/auth/**").permitAll() .requestMatchers("/api/v1/member/**").authenticated() - .requestMatchers("/api/v1/contest/{contest_id}/team/**").authenticated() - .requestMatchers("/api/v2/apply/**").authenticated() // 이외의 모든 요청은 인증 정보 필요 .anyRequest().permitAll()); From e32a40cdcc467d55b316e9487b910866805496df Mon Sep 17 00:00:00 2001 From: dl-00-e8 Date: Mon, 29 Sep 2025 00:38:10 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20Kakao=20Login=20API=20=EA=B0=9C?= =?UTF-8?q?=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/member/MemberService.java | 33 +++++---- .../presentation/member/MemberController.java | 70 ++++++++++++++----- .../inframodule/oauth/kakao/KakaoClient.java | 14 +++- .../inframodule/oauth/kakao/KakaoProfile.java | 2 - 4 files changed, 83 insertions(+), 36 deletions(-) diff --git a/api-module/src/main/java/hongik/triple/apimodule/application/member/MemberService.java b/api-module/src/main/java/hongik/triple/apimodule/application/member/MemberService.java index 16b2052..ec57f62 100644 --- a/api-module/src/main/java/hongik/triple/apimodule/application/member/MemberService.java +++ b/api-module/src/main/java/hongik/triple/apimodule/application/member/MemberService.java @@ -12,6 +12,7 @@ import hongik.triple.inframodule.oauth.kakao.KakaoToken; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Service @@ -23,25 +24,32 @@ public class MemberService { private final KakaoClient kakaoClient; private final GoogleClient googleClient; - @Transactional - public void withdrawal(Member member) { - memberRepository.delete(member); + public String getKakaoLoginUrl(String redirectUri) { + return kakaoClient.getKakaoAuthUrl(redirectUri); } - public MemberRes loginWithKakao(String code, String redirectUri) { - KakaoToken kakaoToken = kakaoClient.getKakaoAccessToken(code, redirectUri); - KakaoProfile kakaoProfile = kakaoClient.getMemberInfo(kakaoToken); + public String getGoogleLoginUrl(String redirectUri) { + return ""; // TODO: 추후 구현 예정 + } - return register(kakaoProfile.kakao_account().email(), kakaoProfile.properties().nickname()); + public KakaoProfile loginWithKakao(String authorizationCode) { + KakaoToken kakaoToken = kakaoClient.getKakaoAccessToken(authorizationCode); + return kakaoClient.getMemberInfo(kakaoToken); } - public MemberRes loginWithGoogle(String accessToken, String redirectUri) { - GoogleToken googleToken = googleClient.getGoogleAccessToken(accessToken, redirectUri); + public MemberRes loginWithGoogle(String authorizationCode) { + GoogleToken googleToken = googleClient.getGoogleAccessToken(authorizationCode); GoogleProfile googleProfile = googleClient.getMemberInfo(googleToken); return register(googleProfile.email(), googleProfile.name()); } + @Transactional + public void withdrawal(Member member) { + memberRepository.delete(member); + } + + @Transactional public void logout() { } @@ -66,8 +74,9 @@ public MemberRes updateProfile(Member member, MemberReq memberReq) { .build(); } - @Transactional - protected MemberRes register(String email, String nickname) { + // TODO: DB 회원가입 실패 시, 카카오에서도 회원 가입 실패로 보상 트랜잭션 처리 필요 + @Transactional // 독립적인 트랜잭션으로 실행, 상위 트랜잭션은 읽기 트랜잭션으로 유지 + public MemberRes register(String email, String nickname) { if (email == null || email.isEmpty()) { throw new IllegalArgumentException("Email cannot be null or empty"); } @@ -83,7 +92,7 @@ protected MemberRes register(String email, String nickname) { .name(member.getName()) .build()) .orElseGet(() -> { - Member newMember = new Member(email, nickname); + Member newMember = new Member(nickname, email); Member saveMember = memberRepository.save(newMember); return MemberRes.builder() .id(saveMember.getMemberId()) diff --git a/api-module/src/main/java/hongik/triple/apimodule/presentation/member/MemberController.java b/api-module/src/main/java/hongik/triple/apimodule/presentation/member/MemberController.java index 006d920..b78a09c 100644 --- a/api-module/src/main/java/hongik/triple/apimodule/presentation/member/MemberController.java +++ b/api-module/src/main/java/hongik/triple/apimodule/presentation/member/MemberController.java @@ -5,58 +5,90 @@ import hongik.triple.apimodule.global.security.PrincipalDetails; import hongik.triple.commonmodule.dto.member.MemberReq; import hongik.triple.commonmodule.dto.member.MemberRes; +import hongik.triple.inframodule.oauth.kakao.KakaoProfile; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @RestController -@RequestMapping("/api/v1/member") +@RequestMapping("/api/v1") @RequiredArgsConstructor @Tag(name = "Member", description = "회원 관련 API") public class MemberController { private final MemberService memberService; - /** - * 로그인/회원가입 API - OAuth2 제공자에 따라 진행되며, 기존 로그인 정보 유무에 따라 회원가입 또는 로그인 처리 - * @param provider OAuth2 제공자 (예: kakao, google 등) - * @return 회원 정보 응답 (MemberRes) - */ - @PostMapping("/login") - public ApplicationResponse login(@RequestParam(name = "provider") String provider, - @RequestParam(name = "redirect-uri") String redirectUri) { + @GetMapping("/auth/login") + public ResponseEntity redirectLoginPage( + @RequestParam(name = "provider") String provider, + @RequestParam(name = "redirect-uri", required = false) String redirectUri) { // 회원가입 로직 if(provider.equals("kakao")) { // 카카오 로그인 로직 - return ApplicationResponse.ok(memberService.loginWithKakao(provider, redirectUri)); + String kakaoAuthUrl = memberService.getKakaoLoginUrl(redirectUri); + HttpHeaders headers = new HttpHeaders(); + headers.add("Location", kakaoAuthUrl); + return new ResponseEntity<>(headers, HttpStatus.FOUND); } else if(provider.equals("google")) { // 구글 로그인 로직 - return ApplicationResponse.ok(memberService.loginWithGoogle(provider, redirectUri)); + String googleAuthUrl = memberService.getGoogleLoginUrl(redirectUri); + HttpHeaders headers = new HttpHeaders(); + headers.add("Location", googleAuthUrl); + return new ResponseEntity<>(headers, HttpStatus.FOUND); } else { throw new IllegalArgumentException("지원하지 않는 로그인 제공자입니다."); } } - @PostMapping("/withdrawal") - public void withdrawal(@AuthenticationPrincipal PrincipalDetails principalDetails) { + /** + * 카카오 로그인/회원가입 API - 기존 로그인 정보 유무에 따라 회원가입 또는 로그인 처리 + * @return 회원 정보 응답 (MemberRes) + */ + @GetMapping("/auth/kakao/login") + public ApplicationResponse loginWithKakao( + @RequestParam(name = "code") String authorizationCode) { + KakaoProfile kakaoProfile = memberService.loginWithKakao(authorizationCode); + return ApplicationResponse.ok(memberService.register(kakaoProfile.kakao_account().email(), kakaoProfile.properties().nickname())); + } + + /** + * 구글 로그인/회원가입 API - 기존 로그인 정보 유무에 따라 회원가입 또는 로그인 처리 + * @return 회원 정보 응답 (MemberRes) + */ + @PostMapping("/auth/google/login") + public ApplicationResponse loginWithGoogle( + @RequestParam(name = "code") String authorizationCode) { + return ApplicationResponse.ok(memberService.loginWithGoogle(authorizationCode)); + } + + @PostMapping("/member/withdrawal") + public void withdrawal( + @AuthenticationPrincipal PrincipalDetails principalDetails) { // 회원탈퇴 로직 memberService.withdrawal(principalDetails.getMember()); } - @PostMapping("/logout") - public void logout(@AuthenticationPrincipal PrincipalDetails principalDetails) { + @PostMapping("/member/logout") + public void logout( + @AuthenticationPrincipal PrincipalDetails principalDetails) { memberService.logout(); } - @PostMapping("/profile") - public void getProfile(@AuthenticationPrincipal PrincipalDetails principalDetails) { + @PostMapping("/member/profile") + public void getProfile( + @AuthenticationPrincipal PrincipalDetails principalDetails) { memberService.getProfile(principalDetails.getMember()); } - @PatchMapping("/update") - public void updateProfile(@AuthenticationPrincipal PrincipalDetails principalDetails, @RequestBody MemberReq req) { + @PatchMapping("/member/update") + public void updateProfile( + @AuthenticationPrincipal PrincipalDetails principalDetails, + @RequestBody MemberReq req) { memberService.updateProfile(principalDetails.getMember(), req); } } diff --git a/infra-module/src/main/java/hongik/triple/inframodule/oauth/kakao/KakaoClient.java b/infra-module/src/main/java/hongik/triple/inframodule/oauth/kakao/KakaoClient.java index 7608f6f..db40bd7 100644 --- a/infra-module/src/main/java/hongik/triple/inframodule/oauth/kakao/KakaoClient.java +++ b/infra-module/src/main/java/hongik/triple/inframodule/oauth/kakao/KakaoClient.java @@ -30,12 +30,21 @@ public class KakaoClient { @Value("${spring.security.oauth2.client.provider.kakao.user-info-uri}") private String kakaoUserInfoUri; + @Value("${spring.security.oauth2.client.registration.kakao.redirect-uri}") + private String kakaoRedirectUri; + + public String getKakaoAuthUrl(String redirectUri) { + return "https://kauth.kakao.com/oauth/authorize?client_id=" + kakaoClientId + + "&redirect_uri=" + kakaoRedirectUri + + "&response_type=code"; + } + /** * 카카오 서버에 인가코드 기반으로 사용자의 토큰 정보를 조회하는 메소드 * @param code - 카카오에서 발급해준 인가 코드 * @return - 카카오에서 반환한 응답 토큰 객체 */ - public KakaoToken getKakaoAccessToken(String code, String redirectUri) { + public KakaoToken getKakaoAccessToken(String code) { // 요청 보낼 객체 기본 생성 WebClient webClient = WebClient.create(kakaoTokenUri); @@ -43,7 +52,7 @@ public KakaoToken getKakaoAccessToken(String code, String redirectUri) { MultiValueMap params = new LinkedMultiValueMap<>(); params.add("grant_type", kakaoGrantType); params.add("client_id", kakaoClientId); - params.add("redirect_uri", redirectUri); + params.add("redirect_uri", kakaoRedirectUri); params.add("code", code); params.add("client_secret", kakaoClientSecret); @@ -86,7 +95,6 @@ public KakaoProfile getMemberInfo(KakaoToken kakaoToken) { KakaoProfile kakaoProfile; try { kakaoProfile = objectMapper.readValue(response, KakaoProfile.class); - } catch (Exception e) { throw new RuntimeException(e); } diff --git a/infra-module/src/main/java/hongik/triple/inframodule/oauth/kakao/KakaoProfile.java b/infra-module/src/main/java/hongik/triple/inframodule/oauth/kakao/KakaoProfile.java index a6d149e..020929d 100644 --- a/infra-module/src/main/java/hongik/triple/inframodule/oauth/kakao/KakaoProfile.java +++ b/infra-module/src/main/java/hongik/triple/inframodule/oauth/kakao/KakaoProfile.java @@ -1,8 +1,6 @@ package hongik.triple.inframodule.oauth.kakao; public record KakaoProfile( - // 2023년 12월까지 없었던 것으로 보이는 데이터인데, 현재 계속 조회됨. (포럼에 문의된 상황) - Boolean setPrivacyInfo, Long id, String connected_at, Properties properties, From b428efe3b441d637d1a6c7b252297ad33f02153b Mon Sep 17 00:00:00 2001 From: dl-00-e8 Date: Mon, 29 Sep 2025 00:38:34 +0900 Subject: [PATCH 04/10] =?UTF-8?q?refactor:=20=EB=AF=B8=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=20import=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/hongik/triple/apimodule/ApiModuleApplication.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/api-module/src/main/java/hongik/triple/apimodule/ApiModuleApplication.java b/api-module/src/main/java/hongik/triple/apimodule/ApiModuleApplication.java index 007f79a..65388a7 100644 --- a/api-module/src/main/java/hongik/triple/apimodule/ApiModuleApplication.java +++ b/api-module/src/main/java/hongik/triple/apimodule/ApiModuleApplication.java @@ -6,8 +6,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.domain.EntityScan; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @Slf4j @SpringBootApplication(scanBasePackageClasses = { From d1efba222c50600bd37b4ffaa3341ae94439eb48 Mon Sep 17 00:00:00 2001 From: dl-00-e8 Date: Mon, 29 Sep 2025 00:40:09 +0900 Subject: [PATCH 05/10] =?UTF-8?q?refactor:=20=EB=AF=B8=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=20parameter=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../triple/apimodule/application/member/MemberService.java | 7 +++---- .../apimodule/presentation/member/MemberController.java | 7 +++---- .../hongik/triple/inframodule/oauth/kakao/KakaoClient.java | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/api-module/src/main/java/hongik/triple/apimodule/application/member/MemberService.java b/api-module/src/main/java/hongik/triple/apimodule/application/member/MemberService.java index ec57f62..a78b8ae 100644 --- a/api-module/src/main/java/hongik/triple/apimodule/application/member/MemberService.java +++ b/api-module/src/main/java/hongik/triple/apimodule/application/member/MemberService.java @@ -12,7 +12,6 @@ import hongik.triple.inframodule.oauth.kakao.KakaoToken; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Service @@ -24,11 +23,11 @@ public class MemberService { private final KakaoClient kakaoClient; private final GoogleClient googleClient; - public String getKakaoLoginUrl(String redirectUri) { - return kakaoClient.getKakaoAuthUrl(redirectUri); + public String getKakaoLoginUrl() { + return kakaoClient.getKakaoAuthUrl(); } - public String getGoogleLoginUrl(String redirectUri) { + public String getGoogleLoginUrl() { return ""; // TODO: 추후 구현 예정 } diff --git a/api-module/src/main/java/hongik/triple/apimodule/presentation/member/MemberController.java b/api-module/src/main/java/hongik/triple/apimodule/presentation/member/MemberController.java index b78a09c..ec3e737 100644 --- a/api-module/src/main/java/hongik/triple/apimodule/presentation/member/MemberController.java +++ b/api-module/src/main/java/hongik/triple/apimodule/presentation/member/MemberController.java @@ -24,18 +24,17 @@ public class MemberController { @GetMapping("/auth/login") public ResponseEntity redirectLoginPage( - @RequestParam(name = "provider") String provider, - @RequestParam(name = "redirect-uri", required = false) String redirectUri) { + @RequestParam(name = "provider") String provider) { // 회원가입 로직 if(provider.equals("kakao")) { // 카카오 로그인 로직 - String kakaoAuthUrl = memberService.getKakaoLoginUrl(redirectUri); + String kakaoAuthUrl = memberService.getKakaoLoginUrl(); HttpHeaders headers = new HttpHeaders(); headers.add("Location", kakaoAuthUrl); return new ResponseEntity<>(headers, HttpStatus.FOUND); } else if(provider.equals("google")) { // 구글 로그인 로직 - String googleAuthUrl = memberService.getGoogleLoginUrl(redirectUri); + String googleAuthUrl = memberService.getGoogleLoginUrl(); HttpHeaders headers = new HttpHeaders(); headers.add("Location", googleAuthUrl); return new ResponseEntity<>(headers, HttpStatus.FOUND); diff --git a/infra-module/src/main/java/hongik/triple/inframodule/oauth/kakao/KakaoClient.java b/infra-module/src/main/java/hongik/triple/inframodule/oauth/kakao/KakaoClient.java index db40bd7..09e032a 100644 --- a/infra-module/src/main/java/hongik/triple/inframodule/oauth/kakao/KakaoClient.java +++ b/infra-module/src/main/java/hongik/triple/inframodule/oauth/kakao/KakaoClient.java @@ -33,7 +33,7 @@ public class KakaoClient { @Value("${spring.security.oauth2.client.registration.kakao.redirect-uri}") private String kakaoRedirectUri; - public String getKakaoAuthUrl(String redirectUri) { + public String getKakaoAuthUrl() { return "https://kauth.kakao.com/oauth/authorize?client_id=" + kakaoClientId + "&redirect_uri=" + kakaoRedirectUri + "&response_type=code"; From 1fd0e47e60a4a42c55f8912d5261b4da92b058e3 Mon Sep 17 00:00:00 2001 From: dl-00-e8 Date: Mon, 29 Sep 2025 12:12:28 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat:=20=EA=B5=AC=EA=B8=80=20=EC=86=8C?= =?UTF-8?q?=EC=85=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20API=20=EA=B0=9C?= =?UTF-8?q?=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/member/MemberService.java | 8 +++----- .../presentation/member/MemberController.java | 7 ++++--- .../inframodule/oauth/google/GoogleClient.java | 15 +++++++++++++-- .../inframodule/oauth/google/GoogleProfile.java | 3 ++- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/api-module/src/main/java/hongik/triple/apimodule/application/member/MemberService.java b/api-module/src/main/java/hongik/triple/apimodule/application/member/MemberService.java index a78b8ae..41009c9 100644 --- a/api-module/src/main/java/hongik/triple/apimodule/application/member/MemberService.java +++ b/api-module/src/main/java/hongik/triple/apimodule/application/member/MemberService.java @@ -28,7 +28,7 @@ public String getKakaoLoginUrl() { } public String getGoogleLoginUrl() { - return ""; // TODO: 추후 구현 예정 + return googleClient.getGoogleAuthUrl(); } public KakaoProfile loginWithKakao(String authorizationCode) { @@ -36,11 +36,9 @@ public KakaoProfile loginWithKakao(String authorizationCode) { return kakaoClient.getMemberInfo(kakaoToken); } - public MemberRes loginWithGoogle(String authorizationCode) { + public GoogleProfile loginWithGoogle(String authorizationCode) { GoogleToken googleToken = googleClient.getGoogleAccessToken(authorizationCode); - GoogleProfile googleProfile = googleClient.getMemberInfo(googleToken); - - return register(googleProfile.email(), googleProfile.name()); + return googleClient.getMemberInfo(googleToken); } @Transactional diff --git a/api-module/src/main/java/hongik/triple/apimodule/presentation/member/MemberController.java b/api-module/src/main/java/hongik/triple/apimodule/presentation/member/MemberController.java index ec3e737..f2af0b9 100644 --- a/api-module/src/main/java/hongik/triple/apimodule/presentation/member/MemberController.java +++ b/api-module/src/main/java/hongik/triple/apimodule/presentation/member/MemberController.java @@ -5,6 +5,7 @@ import hongik.triple.apimodule.global.security.PrincipalDetails; import hongik.triple.commonmodule.dto.member.MemberReq; import hongik.triple.commonmodule.dto.member.MemberRes; +import hongik.triple.inframodule.oauth.google.GoogleProfile; import hongik.triple.inframodule.oauth.kakao.KakaoProfile; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -58,10 +59,11 @@ public ApplicationResponse loginWithKakao( * 구글 로그인/회원가입 API - 기존 로그인 정보 유무에 따라 회원가입 또는 로그인 처리 * @return 회원 정보 응답 (MemberRes) */ - @PostMapping("/auth/google/login") + @GetMapping("/auth/google/login") public ApplicationResponse loginWithGoogle( @RequestParam(name = "code") String authorizationCode) { - return ApplicationResponse.ok(memberService.loginWithGoogle(authorizationCode)); + GoogleProfile googleProfile = memberService.loginWithGoogle(authorizationCode); + return ApplicationResponse.ok(memberService.register(googleProfile.email(), googleProfile.name())); } @PostMapping("/member/withdrawal") @@ -71,7 +73,6 @@ public void withdrawal( memberService.withdrawal(principalDetails.getMember()); } - @PostMapping("/member/logout") public void logout( @AuthenticationPrincipal PrincipalDetails principalDetails) { diff --git a/infra-module/src/main/java/hongik/triple/inframodule/oauth/google/GoogleClient.java b/infra-module/src/main/java/hongik/triple/inframodule/oauth/google/GoogleClient.java index 6770fc2..8a71a40 100644 --- a/infra-module/src/main/java/hongik/triple/inframodule/oauth/google/GoogleClient.java +++ b/infra-module/src/main/java/hongik/triple/inframodule/oauth/google/GoogleClient.java @@ -30,17 +30,27 @@ public class GoogleClient { @Value("${spring.security.oauth2.client.provider.google.user-info-uri}") private String googleUserInfoUri; + @Value("${spring.security.oauth2.client.registration.google.redirect-uri}") + private String googleRedirectUri; + + public String getGoogleAuthUrl() { + return "https://accounts.google.com/o/oauth2/v2/auth?client_id=" + googleClientId + + "&redirect_uri=" + googleRedirectUri + + "&response_type=code" + + "&scope=email profile"; + } + /** * 인가코드 기반으로 Google access token 발급 */ - public GoogleToken getGoogleAccessToken(String code, String redirectUri) { + public GoogleToken getGoogleAccessToken(String code) { WebClient webClient = WebClient.create(googleTokenUri); MultiValueMap params = new LinkedMultiValueMap<>(); params.add("grant_type", googleGrantType); params.add("client_id", googleClientId); params.add("client_secret", googleClientSecret); - params.add("redirect_uri", redirectUri); + params.add("redirect_uri", googleRedirectUri); params.add("code", code); String response = webClient.post() @@ -76,6 +86,7 @@ public GoogleProfile getMemberInfo(GoogleToken googleToken) { try { return objectMapper.readValue(response, GoogleProfile.class); } catch (Exception e) { + System.out.println(e); throw new RuntimeException("Failed to parse Google profile", e); } } diff --git a/infra-module/src/main/java/hongik/triple/inframodule/oauth/google/GoogleProfile.java b/infra-module/src/main/java/hongik/triple/inframodule/oauth/google/GoogleProfile.java index c105212..34ebd0a 100644 --- a/infra-module/src/main/java/hongik/triple/inframodule/oauth/google/GoogleProfile.java +++ b/infra-module/src/main/java/hongik/triple/inframodule/oauth/google/GoogleProfile.java @@ -8,5 +8,6 @@ public record GoogleProfile( String picture, String email, boolean email_verified, - String locale + String locale, + String hd // hosted domain 추가 ) {} \ No newline at end of file From 367f415d1fdd7193fed5bbdeb1847c1e46a0e95c Mon Sep 17 00:00:00 2001 From: dl-00-e8 Date: Mon, 29 Sep 2025 12:45:01 +0900 Subject: [PATCH 07/10] =?UTF-8?q?fix:=20Security=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EB=B0=A9=EC=8B=9D=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/member/MemberService.java | 33 +++++----- .../global/security/PrincipalDetails.java | 15 +---- .../global/security/jwt/JwtFilter.java | 1 - .../global/security/jwt/TokenProvider.java | 61 ++++++------------- .../presentation/member/MemberController.java | 11 ++-- .../commonmodule/dto/member/MemberRes.java | 5 +- .../commonmodule/enumerate/MemberType.java | 6 ++ .../domainmodule/domain/member/Member.java | 23 +++++-- .../member/repository/MemberRepository.java | 4 ++ 9 files changed, 75 insertions(+), 84 deletions(-) create mode 100644 common-module/src/main/java/hongik/triple/commonmodule/enumerate/MemberType.java diff --git a/api-module/src/main/java/hongik/triple/apimodule/application/member/MemberService.java b/api-module/src/main/java/hongik/triple/apimodule/application/member/MemberService.java index 41009c9..2d49a1c 100644 --- a/api-module/src/main/java/hongik/triple/apimodule/application/member/MemberService.java +++ b/api-module/src/main/java/hongik/triple/apimodule/application/member/MemberService.java @@ -1,7 +1,9 @@ package hongik.triple.apimodule.application.member; +import hongik.triple.apimodule.global.security.jwt.TokenProvider; import hongik.triple.commonmodule.dto.member.MemberReq; import hongik.triple.commonmodule.dto.member.MemberRes; +import hongik.triple.commonmodule.enumerate.MemberType; import hongik.triple.domainmodule.domain.member.Member; import hongik.triple.domainmodule.domain.member.repository.MemberRepository; import hongik.triple.inframodule.oauth.google.GoogleClient; @@ -22,6 +24,7 @@ public class MemberService { private final MemberRepository memberRepository; private final KakaoClient kakaoClient; private final GoogleClient googleClient; + private final TokenProvider tokenProvider; public String getKakaoLoginUrl() { return kakaoClient.getKakaoAuthUrl(); @@ -61,7 +64,7 @@ public MemberRes getProfile(Member member) { @Transactional public MemberRes updateProfile(Member member, MemberReq memberReq) { - member.update(memberReq.name(), memberReq.skin_type()); + member.updateSkinType(memberReq.skin_type()); Member updateMember = memberRepository.save(member); return MemberRes.builder() @@ -73,7 +76,7 @@ public MemberRes updateProfile(Member member, MemberReq memberReq) { // TODO: DB 회원가입 실패 시, 카카오에서도 회원 가입 실패로 보상 트랜잭션 처리 필요 @Transactional // 독립적인 트랜잭션으로 실행, 상위 트랜잭션은 읽기 트랜잭션으로 유지 - public MemberRes register(String email, String nickname) { + public MemberRes register(String email, String nickname, MemberType memberType) { if (email == null || email.isEmpty()) { throw new IllegalArgumentException("Email cannot be null or empty"); } @@ -81,21 +84,19 @@ public MemberRes register(String email, String nickname) { throw new IllegalArgumentException("Nickname cannot be null or empty"); } - return memberRepository.findByEmail(email) - .map(member -> - MemberRes.builder() - .id(member.getMemberId()) - .email(member.getEmail()) - .name(member.getName()) - .build()) + Member member = memberRepository.findByEmail(email) .orElseGet(() -> { - Member newMember = new Member(nickname, email); - Member saveMember = memberRepository.save(newMember); - return MemberRes.builder() - .id(saveMember.getMemberId()) - .email(saveMember.getEmail()) - .name(saveMember.getName()) - .build(); + Member newMember = new Member(nickname, email, memberType); + return memberRepository.save(newMember); }); + + String accessToken = tokenProvider.createToken(member).accessToken(); + + return MemberRes.builder() + .id(member.getMemberId()) + .email(member.getEmail()) + .name(member.getName()) + .accessToken(accessToken) + .build(); } } \ No newline at end of file diff --git a/api-module/src/main/java/hongik/triple/apimodule/global/security/PrincipalDetails.java b/api-module/src/main/java/hongik/triple/apimodule/global/security/PrincipalDetails.java index 4d69fef..7180dab 100644 --- a/api-module/src/main/java/hongik/triple/apimodule/global/security/PrincipalDetails.java +++ b/api-module/src/main/java/hongik/triple/apimodule/global/security/PrincipalDetails.java @@ -30,8 +30,8 @@ public PrincipalDetails(Member member, Map attributes) { // 권한 정보 반환 (GENERAL, ADMIN 중 하나) @Override public Collection getAuthorities() { - Collection authorities = new ArrayList(); - authorities.add(new SimpleGrantedAuthority(member.getMemberType())); + Collection authorities = new ArrayList<>(); + authorities.add(new SimpleGrantedAuthority("ROLE_" + member.getMemberType().name())); return authorities; } @@ -70,15 +70,4 @@ public boolean isCredentialsNonExpired() { public boolean isEnabled() { return true; } - -// OAuth2User 인터페이스 메서드 (필요시 구현) -// @Override -// public String getName() { -// return member.getName(); -// } -// -// @Override -// public Map getAttributes() { -// return attributes; -// } } diff --git a/api-module/src/main/java/hongik/triple/apimodule/global/security/jwt/JwtFilter.java b/api-module/src/main/java/hongik/triple/apimodule/global/security/jwt/JwtFilter.java index f474bf5..db0b66e 100644 --- a/api-module/src/main/java/hongik/triple/apimodule/global/security/jwt/JwtFilter.java +++ b/api-module/src/main/java/hongik/triple/apimodule/global/security/jwt/JwtFilter.java @@ -24,7 +24,6 @@ public class JwtFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = resolveToken(request); - // String requestURI = request.getRequestURI(); // 토큰이 존재할 경우, Authentication에 인증 정보 저장 및 로그 출력 if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) { diff --git a/api-module/src/main/java/hongik/triple/apimodule/global/security/jwt/TokenProvider.java b/api-module/src/main/java/hongik/triple/apimodule/global/security/jwt/TokenProvider.java index 8f7be35..9eb33ad 100644 --- a/api-module/src/main/java/hongik/triple/apimodule/global/security/jwt/TokenProvider.java +++ b/api-module/src/main/java/hongik/triple/apimodule/global/security/jwt/TokenProvider.java @@ -1,5 +1,7 @@ package hongik.triple.apimodule.global.security.jwt; +import hongik.triple.apimodule.global.security.PrincipalDetails; +import hongik.triple.commonmodule.enumerate.MemberType; import hongik.triple.commonmodule.exception.ApplicationException; import hongik.triple.commonmodule.exception.ErrorCode; import hongik.triple.domainmodule.domain.member.Member; @@ -46,12 +48,14 @@ protected void init() { * @return 생성된 액세스 토큰 정보 반환 */ private String createAccessToken(Member member) { - Claims claims = getClaims(member); Date now = new Date(); + Date expirationDate = new Date(now.getTime() + accessTokenExpirationTime); + return Jwts.builder() - .claims(claims) + .subject(member.getEmail()) + .claim("memberType", member.getMemberType().name()) .issuedAt(now) - .expiration(new Date(now.getTime() + accessTokenExpirationTime)) + .expiration(expirationDate) .signWith(key) .compact(); } @@ -84,21 +88,6 @@ public boolean validateToken(String token) { } } - /** - * 리프레쉬 토큰 기반으로 액세스 토큰 재발급 + 리프레쉬 토큰의 유효기간이 액세스 토큰의 유효기간보다 짧을 경우, 리프레쉬 토큰도 재발급 - * @param member - 재발급을 요청한 사용자 정보 - * @param refreshToken - 재발급을 요청했던 리프레쉬 토큰 - * @return 재발급된 액세스 토큰을 담은 TokenDto 객체 반환 - */ - public TokenDto reissue(Member member, String refreshToken) { - // 액세스 토큰 재발급 - String accessToken = createAccessToken(member); - - return TokenDto.builder() - .accessToken(accessToken) - .build(); - } - /** * 토큰에서 정보를 추출해서 Authentication 객체를 반환 * @param token - 액세스 토큰으로, 해당 토큰에서 정보를 추출해서 사용 @@ -106,12 +95,14 @@ public TokenDto reissue(Member member, String refreshToken) { */ public Authentication getAuthentication(String token) { String email = getEmail(token); -// Member member = memberRepository.findMemberByEmailAndMemberTypeAndDeletedAtIsNull(email, memberType) -// .orElseThrow(() -> new ApplicationException(ErrorCode.NOT_FOUND_EXCEPTION)); -// PrincipalDetails principalDetails = new PrincipalDetails(member); -// -// return new UsernamePasswordAuthenticationToken(principalDetails, "", principalDetails.getAuthorities()); - return null; // TODO: update + MemberType memberType = getMemberType(token); + + Member member = memberRepository.findMemberByEmailAndMemberTypeAndDeletedAtIsNull(email, memberType) + .orElseThrow(() -> new ApplicationException(ErrorCode.NOT_FOUND_EXCEPTION)); + + PrincipalDetails principalDetails = new PrincipalDetails(member); + + return new UsernamePasswordAuthenticationToken(principalDetails, "", principalDetails.getAuthorities()); } /** @@ -128,28 +119,14 @@ public String getEmail(String token) { .getSubject(); } - /** - * 토큰의 만료기한 반환 - * @param token - 일반적으로 액세스 토큰 / 토큰 재발급 요청 시에는 리프레쉬 토큰이 들어옴 - * @return 해당 토큰의 만료정보를 반환 - */ - public Date getExpiration(String token) { - return Jwts.parser() + public MemberType getMemberType(String token) { + String memberTypeStr = Jwts.parser() .verifyWith(key) .build() .parseSignedClaims(token) .getPayload() - .getExpiration(); - } + .get("memberType", String.class); - /** - * Claims 정보 생성 - * @param member - 사용자 정보 중 사용자를 구분할 수 있는 정보 두 개를 활용함 - * @return 사용자 구분 정보인 이메일과 역할을 저장한 Claims 객체 반환 - */ - private Claims getClaims(Member member) { - return Jwts.claims() - .subject(member.getEmail()) - .build(); + return MemberType.valueOf(memberTypeStr); } } diff --git a/api-module/src/main/java/hongik/triple/apimodule/presentation/member/MemberController.java b/api-module/src/main/java/hongik/triple/apimodule/presentation/member/MemberController.java index f2af0b9..2b14ebb 100644 --- a/api-module/src/main/java/hongik/triple/apimodule/presentation/member/MemberController.java +++ b/api-module/src/main/java/hongik/triple/apimodule/presentation/member/MemberController.java @@ -5,6 +5,7 @@ import hongik.triple.apimodule.global.security.PrincipalDetails; import hongik.triple.commonmodule.dto.member.MemberReq; import hongik.triple.commonmodule.dto.member.MemberRes; +import hongik.triple.commonmodule.enumerate.MemberType; import hongik.triple.inframodule.oauth.google.GoogleProfile; import hongik.triple.inframodule.oauth.kakao.KakaoProfile; import io.swagger.v3.oas.annotations.tags.Tag; @@ -52,7 +53,7 @@ public ResponseEntity redirectLoginPage( public ApplicationResponse loginWithKakao( @RequestParam(name = "code") String authorizationCode) { KakaoProfile kakaoProfile = memberService.loginWithKakao(authorizationCode); - return ApplicationResponse.ok(memberService.register(kakaoProfile.kakao_account().email(), kakaoProfile.properties().nickname())); + return ApplicationResponse.ok(memberService.register(kakaoProfile.kakao_account().email(), kakaoProfile.properties().nickname(), MemberType.KAKAO)); } /** @@ -63,7 +64,7 @@ public ApplicationResponse loginWithKakao( public ApplicationResponse loginWithGoogle( @RequestParam(name = "code") String authorizationCode) { GoogleProfile googleProfile = memberService.loginWithGoogle(authorizationCode); - return ApplicationResponse.ok(memberService.register(googleProfile.email(), googleProfile.name())); + return ApplicationResponse.ok(memberService.register(googleProfile.email(), googleProfile.name(), MemberType.GOOGLE)); } @PostMapping("/member/withdrawal") @@ -79,10 +80,10 @@ public void logout( memberService.logout(); } - @PostMapping("/member/profile") - public void getProfile( + @GetMapping("/member/profile") + public ApplicationResponse getProfile( @AuthenticationPrincipal PrincipalDetails principalDetails) { - memberService.getProfile(principalDetails.getMember()); + return ApplicationResponse.ok(memberService.getProfile(principalDetails.getMember())); } @PatchMapping("/member/update") diff --git a/common-module/src/main/java/hongik/triple/commonmodule/dto/member/MemberRes.java b/common-module/src/main/java/hongik/triple/commonmodule/dto/member/MemberRes.java index 964faee..a1886d9 100644 --- a/common-module/src/main/java/hongik/triple/commonmodule/dto/member/MemberRes.java +++ b/common-module/src/main/java/hongik/triple/commonmodule/dto/member/MemberRes.java @@ -1,8 +1,10 @@ package hongik.triple.commonmodule.dto.member; +import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Builder; @Builder +@JsonInclude(JsonInclude.Include.NON_NULL) public record MemberRes( Long id, String email, @@ -11,6 +13,7 @@ public record MemberRes( String thumbnailImageUrl, String nickname, String profileImagePath, - String thumbnailImagePath + String thumbnailImagePath, + String accessToken // JWT Access Token ) { } diff --git a/common-module/src/main/java/hongik/triple/commonmodule/enumerate/MemberType.java b/common-module/src/main/java/hongik/triple/commonmodule/enumerate/MemberType.java new file mode 100644 index 0000000..5b3c66c --- /dev/null +++ b/common-module/src/main/java/hongik/triple/commonmodule/enumerate/MemberType.java @@ -0,0 +1,6 @@ +package hongik.triple.commonmodule.enumerate; + +public enum MemberType { + KAKAO, + GOOGLE +} \ No newline at end of file diff --git a/domain-module/src/main/java/hongik/triple/domainmodule/domain/member/Member.java b/domain-module/src/main/java/hongik/triple/domainmodule/domain/member/Member.java index 9fcd97b..072d813 100644 --- a/domain-module/src/main/java/hongik/triple/domainmodule/domain/member/Member.java +++ b/domain-module/src/main/java/hongik/triple/domainmodule/domain/member/Member.java @@ -1,5 +1,7 @@ package hongik.triple.domainmodule.domain.member; +import hongik.triple.commonmodule.enumerate.MemberType; +import hongik.triple.commonmodule.enumerate.SkinType; import hongik.triple.domainmodule.common.BaseTimeEntity; import jakarta.persistence.*; import lombok.Getter; @@ -16,19 +18,28 @@ public class Member extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long memberId; + + @Column(name = "name", nullable = false) private String name; + + @Column(name = "email", nullable = false, unique = true) private String email; - private String memberType; - private String provider; - private String skinType; - public void update(String name, String skinType) { - this.name = name; + @Column(name = "member_type", nullable = false) + @Enumerated(EnumType.STRING) + private MemberType memberType; + + @Column(name = "skin_type") + private String skinType; // SkinType enum의 값을 문자열로 저장 + + public void updateSkinType(String skinType) { this.skinType = skinType; } - public Member(String name, String email) { + public Member(String name, String email, MemberType memberType) { this.name = name; this.email = email; + this.memberType = memberType; + this.skinType = "normal"; // 기본값 설정 } } diff --git a/domain-module/src/main/java/hongik/triple/domainmodule/domain/member/repository/MemberRepository.java b/domain-module/src/main/java/hongik/triple/domainmodule/domain/member/repository/MemberRepository.java index 5864e87..a79b7b7 100644 --- a/domain-module/src/main/java/hongik/triple/domainmodule/domain/member/repository/MemberRepository.java +++ b/domain-module/src/main/java/hongik/triple/domainmodule/domain/member/repository/MemberRepository.java @@ -1,5 +1,6 @@ package hongik.triple.domainmodule.domain.member.repository; +import hongik.triple.commonmodule.enumerate.MemberType; import hongik.triple.domainmodule.domain.member.Member; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -11,4 +12,7 @@ public interface MemberRepository extends JpaRepository { // 이메일로 회원 조회 Optional findByEmail(String email); + + // 이메일과 회원 유형으로 회원 조회 + Optional findMemberByEmailAndMemberTypeAndDeletedAtIsNull(String email, MemberType memberType); } From d1778ee1a0b36e8a4b600da327f30bfffaaca30f Mon Sep 17 00:00:00 2001 From: dl-00-e8 Date: Mon, 29 Sep 2025 13:11:51 +0900 Subject: [PATCH 08/10] =?UTF-8?q?refactor:=20=ED=94=84=EB=A1=A0=ED=8A=B8?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=9D=B8=EA=B0=80=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EB=A5=BC=20=EB=B0=9B=EC=95=84=20=EB=B0=B1=EC=97=94=EB=93=9C?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=B4=EB=82=B4=EB=8A=94=20=EB=B0=A9=EC=8B=9D?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=EB=8F=84=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/member/MemberService.java | 16 ++++---- .../presentation/member/MemberController.java | 40 ++++++++----------- .../oauth/google/GoogleClient.java | 18 ++++++--- .../inframodule/oauth/kakao/KakaoClient.java | 17 ++++++-- 4 files changed, 51 insertions(+), 40 deletions(-) diff --git a/api-module/src/main/java/hongik/triple/apimodule/application/member/MemberService.java b/api-module/src/main/java/hongik/triple/apimodule/application/member/MemberService.java index 2d49a1c..8c8c42a 100644 --- a/api-module/src/main/java/hongik/triple/apimodule/application/member/MemberService.java +++ b/api-module/src/main/java/hongik/triple/apimodule/application/member/MemberService.java @@ -26,21 +26,21 @@ public class MemberService { private final GoogleClient googleClient; private final TokenProvider tokenProvider; - public String getKakaoLoginUrl() { - return kakaoClient.getKakaoAuthUrl(); + public String getKakaoLoginUrl(String redirectUri) { + return kakaoClient.getKakaoAuthUrl(redirectUri); } - public String getGoogleLoginUrl() { - return googleClient.getGoogleAuthUrl(); + public String getGoogleLoginUrl(String redirectUri) { + return googleClient.getGoogleAuthUrl(redirectUri); } - public KakaoProfile loginWithKakao(String authorizationCode) { - KakaoToken kakaoToken = kakaoClient.getKakaoAccessToken(authorizationCode); + public KakaoProfile loginWithKakao(String authorizationCode, String redirectUri) { + KakaoToken kakaoToken = kakaoClient.getKakaoAccessToken(authorizationCode, redirectUri); return kakaoClient.getMemberInfo(kakaoToken); } - public GoogleProfile loginWithGoogle(String authorizationCode) { - GoogleToken googleToken = googleClient.getGoogleAccessToken(authorizationCode); + public GoogleProfile loginWithGoogle(String authorizationCode, String redirectUri) { + GoogleToken googleToken = googleClient.getGoogleAccessToken(authorizationCode, redirectUri); return googleClient.getMemberInfo(googleToken); } diff --git a/api-module/src/main/java/hongik/triple/apimodule/presentation/member/MemberController.java b/api-module/src/main/java/hongik/triple/apimodule/presentation/member/MemberController.java index 2b14ebb..f46ff26 100644 --- a/api-module/src/main/java/hongik/triple/apimodule/presentation/member/MemberController.java +++ b/api-module/src/main/java/hongik/triple/apimodule/presentation/member/MemberController.java @@ -26,22 +26,20 @@ public class MemberController { @GetMapping("/auth/login") public ResponseEntity redirectLoginPage( - @RequestParam(name = "provider") String provider) { - // 회원가입 로직 - if(provider.equals("kakao")) { - // 카카오 로그인 로직 - String kakaoAuthUrl = memberService.getKakaoLoginUrl(); - HttpHeaders headers = new HttpHeaders(); - headers.add("Location", kakaoAuthUrl); - return new ResponseEntity<>(headers, HttpStatus.FOUND); - } else if(provider.equals("google")) { - // 구글 로그인 로직 - String googleAuthUrl = memberService.getGoogleLoginUrl(); + @RequestParam(name = "provider") String provider, + @RequestParam(name = "redirect-uri", required = false) String redirectUri) { + String authUrl = switch (provider) { + case "kakao" -> memberService.getKakaoLoginUrl(redirectUri); + case "google" -> memberService.getGoogleLoginUrl(redirectUri); + default -> throw new IllegalArgumentException("지원하지 않는 로그인 제공자입니다."); + }; + + if(redirectUri == null || redirectUri.isEmpty()) { HttpHeaders headers = new HttpHeaders(); - headers.add("Location", googleAuthUrl); + headers.add("Location", authUrl); return new ResponseEntity<>(headers, HttpStatus.FOUND); } else { - throw new IllegalArgumentException("지원하지 않는 로그인 제공자입니다."); + return ResponseEntity.ok(ApplicationResponse.ok(authUrl)); } } @@ -51,8 +49,9 @@ public ResponseEntity redirectLoginPage( */ @GetMapping("/auth/kakao/login") public ApplicationResponse loginWithKakao( - @RequestParam(name = "code") String authorizationCode) { - KakaoProfile kakaoProfile = memberService.loginWithKakao(authorizationCode); + @RequestParam(name = "code") String authorizationCode, + @RequestParam(name = "redirect-uri", required = false) String redirectUri) { + KakaoProfile kakaoProfile = memberService.loginWithKakao(authorizationCode, redirectUri); return ApplicationResponse.ok(memberService.register(kakaoProfile.kakao_account().email(), kakaoProfile.properties().nickname(), MemberType.KAKAO)); } @@ -62,8 +61,9 @@ public ApplicationResponse loginWithKakao( */ @GetMapping("/auth/google/login") public ApplicationResponse loginWithGoogle( - @RequestParam(name = "code") String authorizationCode) { - GoogleProfile googleProfile = memberService.loginWithGoogle(authorizationCode); + @RequestParam(name = "code") String authorizationCode, + @RequestParam(name = "redirect-uri", required = false) String redirectUri) { + GoogleProfile googleProfile = memberService.loginWithGoogle(authorizationCode, redirectUri); return ApplicationResponse.ok(memberService.register(googleProfile.email(), googleProfile.name(), MemberType.GOOGLE)); } @@ -74,12 +74,6 @@ public void withdrawal( memberService.withdrawal(principalDetails.getMember()); } - @PostMapping("/member/logout") - public void logout( - @AuthenticationPrincipal PrincipalDetails principalDetails) { - memberService.logout(); - } - @GetMapping("/member/profile") public ApplicationResponse getProfile( @AuthenticationPrincipal PrincipalDetails principalDetails) { diff --git a/infra-module/src/main/java/hongik/triple/inframodule/oauth/google/GoogleClient.java b/infra-module/src/main/java/hongik/triple/inframodule/oauth/google/GoogleClient.java index 8a71a40..12ac1fc 100644 --- a/infra-module/src/main/java/hongik/triple/inframodule/oauth/google/GoogleClient.java +++ b/infra-module/src/main/java/hongik/triple/inframodule/oauth/google/GoogleClient.java @@ -33,9 +33,13 @@ public class GoogleClient { @Value("${spring.security.oauth2.client.registration.google.redirect-uri}") private String googleRedirectUri; - public String getGoogleAuthUrl() { + public String getGoogleAuthUrl(String redirectUri) { + if(redirectUri == null || redirectUri.isEmpty()) { + redirectUri = googleRedirectUri; + } + return "https://accounts.google.com/o/oauth2/v2/auth?client_id=" + googleClientId + - "&redirect_uri=" + googleRedirectUri + + "&redirect_uri=" + redirectUri + "&response_type=code" + "&scope=email profile"; } @@ -43,14 +47,19 @@ public String getGoogleAuthUrl() { /** * 인가코드 기반으로 Google access token 발급 */ - public GoogleToken getGoogleAccessToken(String code) { + public GoogleToken getGoogleAccessToken(String code, String redirectUri) { + // 별도의 리다이렉트 요청 URI 설정이 없을 경우, application.yml에 설정된 값 사용 + if (redirectUri == null || redirectUri.isEmpty()) { + redirectUri = googleRedirectUri; + } + WebClient webClient = WebClient.create(googleTokenUri); MultiValueMap params = new LinkedMultiValueMap<>(); params.add("grant_type", googleGrantType); params.add("client_id", googleClientId); params.add("client_secret", googleClientSecret); - params.add("redirect_uri", googleRedirectUri); + params.add("redirect_uri", redirectUri); params.add("code", code); String response = webClient.post() @@ -86,7 +95,6 @@ public GoogleProfile getMemberInfo(GoogleToken googleToken) { try { return objectMapper.readValue(response, GoogleProfile.class); } catch (Exception e) { - System.out.println(e); throw new RuntimeException("Failed to parse Google profile", e); } } diff --git a/infra-module/src/main/java/hongik/triple/inframodule/oauth/kakao/KakaoClient.java b/infra-module/src/main/java/hongik/triple/inframodule/oauth/kakao/KakaoClient.java index 09e032a..25de8bb 100644 --- a/infra-module/src/main/java/hongik/triple/inframodule/oauth/kakao/KakaoClient.java +++ b/infra-module/src/main/java/hongik/triple/inframodule/oauth/kakao/KakaoClient.java @@ -33,9 +33,13 @@ public class KakaoClient { @Value("${spring.security.oauth2.client.registration.kakao.redirect-uri}") private String kakaoRedirectUri; - public String getKakaoAuthUrl() { + public String getKakaoAuthUrl(String redirectUri) { + if(redirectUri == null || redirectUri.isEmpty()) { + redirectUri = kakaoRedirectUri; + } + return "https://kauth.kakao.com/oauth/authorize?client_id=" + kakaoClientId + - "&redirect_uri=" + kakaoRedirectUri + + "&redirect_uri=" + redirectUri + "&response_type=code"; } @@ -44,7 +48,12 @@ public String getKakaoAuthUrl() { * @param code - 카카오에서 발급해준 인가 코드 * @return - 카카오에서 반환한 응답 토큰 객체 */ - public KakaoToken getKakaoAccessToken(String code) { + public KakaoToken getKakaoAccessToken(String code, String redirectUri) { + // 별도의 리다이렉트 요청 URI 설정이 없을 경우, application.yml에 설정된 값 사용 + if (redirectUri == null || redirectUri.isEmpty()) { + redirectUri = kakaoRedirectUri; + } + // 요청 보낼 객체 기본 생성 WebClient webClient = WebClient.create(kakaoTokenUri); @@ -52,7 +61,7 @@ public KakaoToken getKakaoAccessToken(String code) { MultiValueMap params = new LinkedMultiValueMap<>(); params.add("grant_type", kakaoGrantType); params.add("client_id", kakaoClientId); - params.add("redirect_uri", kakaoRedirectUri); + params.add("redirect_uri", redirectUri); params.add("code", code); params.add("client_secret", kakaoClientSecret); From b463f52d0a2ce2871cdb3d967d9531d98dc407e2 Mon Sep 17 00:00:00 2001 From: dl-00-e8 Date: Mon, 29 Sep 2025 13:18:33 +0900 Subject: [PATCH 09/10] =?UTF-8?q?test:=20=EC=86=8C=EC=85=9C=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EA=B4=80=EB=A0=A8=20=EB=8B=A8=EC=9C=84=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apimodule/member/MemberServiceTest.java | 362 +++++++++++++----- 1 file changed, 256 insertions(+), 106 deletions(-) diff --git a/api-module/src/test/java/hongik/triple/apimodule/member/MemberServiceTest.java b/api-module/src/test/java/hongik/triple/apimodule/member/MemberServiceTest.java index 7e17054..8fadd20 100644 --- a/api-module/src/test/java/hongik/triple/apimodule/member/MemberServiceTest.java +++ b/api-module/src/test/java/hongik/triple/apimodule/member/MemberServiceTest.java @@ -1,14 +1,18 @@ package hongik.triple.apimodule.member; import hongik.triple.apimodule.application.member.MemberService; +import hongik.triple.apimodule.global.security.jwt.TokenProvider; +import hongik.triple.apimodule.global.security.jwt.TokenDto; import hongik.triple.commonmodule.dto.member.MemberReq; import hongik.triple.commonmodule.dto.member.MemberRes; +import hongik.triple.commonmodule.enumerate.MemberType; import hongik.triple.domainmodule.domain.member.Member; import hongik.triple.domainmodule.domain.member.repository.MemberRepository; import hongik.triple.inframodule.oauth.google.GoogleClient; import hongik.triple.inframodule.oauth.google.GoogleProfile; import hongik.triple.inframodule.oauth.google.GoogleToken; import hongik.triple.inframodule.oauth.kakao.KakaoClient; +import hongik.triple.inframodule.oauth.kakao.KakaoProfile; import hongik.triple.inframodule.oauth.kakao.KakaoToken; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -40,116 +44,208 @@ public class MemberServiceTest { @Mock private GoogleClient googleClient; + @Mock + private TokenProvider tokenProvider; + @InjectMocks private MemberService memberService; @Nested - @DisplayName("register()는") - class RegisterTest { + @DisplayName("getKakaoLoginUrl()은") + class GetKakaoLoginUrlTest { @Test - @DisplayName("회원이 없을 경우 새로 등록한다.") - void registerNewMember() { + @DisplayName("카카오 로그인 URL을 반환한다.") + void success() { // given - String email = "new@user.com"; - String name = "NewUser"; - GoogleToken googleToken = new GoogleToken( - "access_token", - "3600", - "Bearer", - "profile email", - "refresh_token", - "id_token" + String redirectUri = "http://localhost:3000/auth/callback"; + String expectedUrl = "https://kauth.kakao.com/oauth/authorize?client_id=xxx&redirect_uri=" + redirectUri + "&response_type=code"; + + given(kakaoClient.getKakaoAuthUrl(redirectUri)) + .willReturn(expectedUrl); + + // when + String result = memberService.getKakaoLoginUrl(redirectUri); + + // then + assertThat(result).isEqualTo(expectedUrl); + verify(kakaoClient, times(1)).getKakaoAuthUrl(redirectUri); + } + } + + @Nested + @DisplayName("getGoogleLoginUrl()은") + class GetGoogleLoginUrlTest { + + @Test + @DisplayName("구글 로그인 URL을 반환한다.") + void success() { + // given + String redirectUri = "http://localhost:3000/auth/callback"; + String expectedUrl = "https://accounts.google.com/o/oauth2/v2/auth?client_id=xxx&redirect_uri=" + redirectUri + "&response_type=code&scope=email profile"; + + given(googleClient.getGoogleAuthUrl(redirectUri)) + .willReturn(expectedUrl); + + // when + String result = memberService.getGoogleLoginUrl(redirectUri); + + // then + assertThat(result).isEqualTo(expectedUrl); + verify(googleClient, times(1)).getGoogleAuthUrl(redirectUri); + } + } + + @Nested + @DisplayName("loginWithKakao()는") + class LoginWithKakaoTest { + + @Test + @DisplayName("카카오 액세스 토큰과 사용자 정보를 정상적으로 조회한다.") + void success() { + // given + String authCode = "kakao_auth_code"; + String redirectUri = "http://localhost:3000/auth/callback"; + + KakaoToken kakaoToken = KakaoToken.builder() + .access_token("kakao_access_token") + .refresh_token("kakao_refresh_token") + .token_type("bearer") + .expires_in(3600) + .build(); + + KakaoProfile.Properties properties = new KakaoProfile.Properties( + "카카오유저", + "https://example.com/profile.jpg", + "https://example.com/thumbnail.jpg" ); - GoogleProfile googleProfile = new GoogleProfile( - "sub_id", - name, - "given", - "family", - "profile.jpg", - email, + + KakaoProfile.KakaoAccount.Profile profile = new KakaoProfile.KakaoAccount.Profile( + "카카오유저", + "https://example.com/thumbnail.jpg", + "https://example.com/profile.jpg", + false, + false + ); + + KakaoProfile.KakaoAccount kakaoAccount = new KakaoProfile.KakaoAccount( + false, + false, + profile, + true, + false, true, - "ko" + true, + "kakao@test.com" ); - Member member = new Member(name, email); - given(googleClient.getGoogleAccessToken(eq("access_token"), anyString())) - .willReturn(googleToken); - given(googleClient.getMemberInfo(eq(googleToken))) - .willReturn(googleProfile); - given(memberRepository.findByEmail(email)) - .willReturn(Optional.empty()); - given(memberRepository.save(any(Member.class))) - .willReturn(member); + KakaoProfile kakaoProfile = new KakaoProfile( + 12345L, + "2024-01-01T00:00:00Z", + properties, + kakaoAccount + ); + + given(kakaoClient.getKakaoAccessToken(authCode, redirectUri)) + .willReturn(kakaoToken); + given(kakaoClient.getMemberInfo(kakaoToken)) + .willReturn(kakaoProfile); // when - MemberRes result = memberService.loginWithGoogle("access_token", "http://localhost/oauth2/callback/google"); + KakaoProfile result = memberService.loginWithKakao(authCode, redirectUri); // then - assertThat(result.email()).isEqualTo(email); - assertThat(result.name()).isEqualTo(name); + assertThat(result.kakao_account().email()).isEqualTo("kakao@test.com"); + assertThat(result.properties().nickname()).isEqualTo("카카오유저"); + assertThat(result.kakao_account().profile().nickname()).isEqualTo("카카오유저"); + verify(kakaoClient, times(1)).getKakaoAccessToken(authCode, redirectUri); + verify(kakaoClient, times(1)).getMemberInfo(kakaoToken); } @Test - @DisplayName("회원이 이미 존재하면 기존 회원을 반환한다.") - void registerExistingMember() { + @DisplayName("카카오 사용자 정보 조회 중 예외가 발생하면 예외를 던진다.") + void throwsExceptionWhenGettingProfile() { // given - String email = "exist@user.com"; - String name = "Existing"; - Member existing = new Member(email, name); // ← 순서 확인 (email, name) + String authCode = "invalid_code"; + String redirectUri = "http://localhost:3000/auth/callback"; + + KakaoToken kakaoToken = KakaoToken.builder() + .access_token("kakao_access_token") + .build(); + + given(kakaoClient.getKakaoAccessToken(authCode, redirectUri)) + .willReturn(kakaoToken); + given(kakaoClient.getMemberInfo(kakaoToken)) + .willThrow(new RuntimeException("카카오 사용자 정보 조회 실패")); + + // when & then + assertThatThrownBy(() -> memberService.loginWithKakao(authCode, redirectUri)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("카카오 사용자 정보"); + } + } + + @Nested + @DisplayName("loginWithGoogle()는") + class LoginWithGoogleTest { + + @Test + @DisplayName("구글 액세스 토큰과 사용자 정보를 정상적으로 조회한다.") + void success() { + // given + String authCode = "google_auth_code"; + String redirectUri = "http://localhost:3000/auth/callback"; + GoogleToken googleToken = new GoogleToken( - "access_token", + "google_access_token", "3600", "Bearer", - "scope", - "refresh", + "profile email", + "google_refresh_token", "id_token" ); + GoogleProfile googleProfile = new GoogleProfile( - "sub", - name, - "given", - "family", - "picture.jpg", - email, + "google_sub_id", + "구글유저", + "구글", + "유저", + "profile.jpg", + "google@test.com", true, - "ko" + "ko", + null ); - given(googleClient.getGoogleAccessToken(eq("access_token"), anyString())) + given(googleClient.getGoogleAccessToken(authCode, redirectUri)) .willReturn(googleToken); - - given(googleClient.getMemberInfo(eq(googleToken))) + given(googleClient.getMemberInfo(googleToken)) .willReturn(googleProfile); - given(memberRepository.findByEmail(email)) - .willReturn(Optional.of(existing)); - // when - MemberRes result = memberService.loginWithGoogle("access_token", "http://localhost/oauth2/callback/google"); + GoogleProfile result = memberService.loginWithGoogle(authCode, redirectUri); // then - assertThat(result.email()).isEqualTo(existing.getEmail()); - assertThat(result.name()).isEqualTo(existing.getName()); + assertThat(result.email()).isEqualTo("google@test.com"); + assertThat(result.name()).isEqualTo("구글유저"); + verify(googleClient, times(1)).getGoogleAccessToken(authCode, redirectUri); + verify(googleClient, times(1)).getMemberInfo(googleToken); } @Test - @DisplayName("Google 사용자 정보 조회 중 예외가 발생하면 예외를 던진다.") - void getGoogleProfileThrowsException() { + @DisplayName("구글 사용자 정보 조회 중 예외가 발생하면 예외를 던진다.") + void throwsExceptionWhenGettingProfile() { // given - String authCode = "dummy_code"; - String redirectUri = "http://localhost/oauth2/callback/google"; + String authCode = "invalid_code"; + String redirectUri = "http://localhost:3000/auth/callback"; GoogleToken googleToken = new GoogleToken( - "dummy_access_token", "3600", "Bearer", "profile email", "dummy_refresh", "dummy_id_token" + "google_access_token", "3600", "Bearer", "profile email", "refresh", "id_token" ); - // getGoogleAccessToken: redirectUri 정확히 일치시켜야 함 - given(googleClient.getGoogleAccessToken(eq(authCode), eq(redirectUri))) + given(googleClient.getGoogleAccessToken(authCode, redirectUri)) .willReturn(googleToken); - - // getMemberInfo에서 예외 발생 - given(googleClient.getMemberInfo(eq(googleToken))) + given(googleClient.getMemberInfo(googleToken)) .willThrow(new RuntimeException("Failed to call Google API")); // when & then @@ -157,56 +253,82 @@ void getGoogleProfileThrowsException() { .isInstanceOf(RuntimeException.class) .hasMessageContaining("Google API"); } + } + + @Nested + @DisplayName("register()는") + class RegisterTest { @Test - @DisplayName("카카오 사용자 정보 조회 중 예외가 발생하면 예외를 던진다.") - void kakaoClientThrowsWhenGettingProfile() { + @DisplayName("신규 회원을 등록하고 토큰을 발급한다.") + void registerNewMember() { // given - String authCode = "dummy_auth_code"; - String redirectUri = "http://localhost/oauth2/callback/kakao"; + String email = "new@user.com"; + String nickname = "NewUser"; + MemberType memberType = MemberType.KAKAO; - KakaoToken kakaoToken = KakaoToken.builder() - .access_token("dummy_access_token") - .refresh_token("dummy_refresh_token") - .token_type("bearer") - .expires_in(3600) - .refresh_token_expires_in(1209600) - .scope("profile account_email") - .build(); + Member newMember = new Member(nickname, email, memberType); + TokenDto tokenDto = new TokenDto("generated_access_token"); - given(kakaoClient.getKakaoAccessToken(authCode, redirectUri)) - .willReturn(kakaoToken); + given(memberRepository.findByEmail(email)) + .willReturn(Optional.empty()); + given(memberRepository.save(any(Member.class))) + .willReturn(newMember); + given(tokenProvider.createToken(any(Member.class))) + .willReturn(tokenDto); - given(kakaoClient.getMemberInfo(kakaoToken)) - .willThrow(new RuntimeException("카카오 사용자 정보 조회 실패")); + // when + MemberRes result = memberService.register(email, nickname, memberType); - // when & then - assertThatThrownBy(() -> memberService.loginWithKakao(authCode, redirectUri)) - .isInstanceOf(RuntimeException.class) - .hasMessageContaining("카카오 사용자 정보"); + // then + assertThat(result.email()).isEqualTo(email); + assertThat(result.name()).isEqualTo(nickname); + assertThat(result.accessToken()).isEqualTo("generated_access_token"); + verify(memberRepository, times(1)).save(any(Member.class)); } - } - - - @Nested - @DisplayName("updateProfile()는") - class UpdateProfileTest { @Test - @DisplayName("정상적으로 회원 정보를 수정한다.") - void success() { + @DisplayName("기존 회원이 존재하면 토큰만 재발급한다.") + void registerExistingMember() { // given - Member member = new Member("email@test.com", "old"); - MemberReq req = new MemberReq("new", "OILY"); + String email = "exist@user.com"; + String nickname = "ExistingUser"; + MemberType memberType = MemberType.GOOGLE; - given(memberRepository.save(any(Member.class))) - .willReturn(member); // save 이후 리턴값 지정 + Member existingMember = new Member(nickname, email, memberType); + TokenDto tokenDto = new TokenDto("new_access_token"); + + given(memberRepository.findByEmail(email)) + .willReturn(Optional.of(existingMember)); + given(tokenProvider.createToken(existingMember)) + .willReturn(tokenDto); // when - MemberRes res = memberService.updateProfile(member, req); + MemberRes result = memberService.register(email, nickname, memberType); // then - assertThat(res.name()).isEqualTo("new"); + assertThat(result.email()).isEqualTo(email); + assertThat(result.name()).isEqualTo(nickname); + assertThat(result.accessToken()).isEqualTo("new_access_token"); + verify(memberRepository, times(0)).save(any(Member.class)); // 저장 안 함 + } + + @Test + @DisplayName("이메일이 null이면 예외를 던진다.") + void throwsExceptionWhenEmailIsNull() { + // when & then + assertThatThrownBy(() -> memberService.register(null, "nickname", MemberType.KAKAO)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Email"); + } + + @Test + @DisplayName("닉네임이 비어있으면 예외를 던진다.") + void throwsExceptionWhenNicknameIsEmpty() { + // when & then + assertThatThrownBy(() -> memberService.register("email@test.com", "", MemberType.KAKAO)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Nickname"); } } @@ -217,15 +339,42 @@ class GetProfileTest { @Test @DisplayName("회원 정보를 반환한다.") void success() { - Member member = new Member("nick", "email@test.com"); + // given + Member member = new Member("nickname", "email@test.com", MemberType.KAKAO); - MemberRes res = memberService.getProfile(member); + // when + MemberRes result = memberService.getProfile(member); - assertThat(res.email()).isEqualTo("email@test.com"); - assertThat(res.name()).isEqualTo("nick"); + // then + assertThat(result.email()).isEqualTo("email@test.com"); + assertThat(result.name()).isEqualTo("nickname"); } } + // TODO: updateProfile() 메서드에 skin_type 필드 추가 후 테스트 케이스 수정 +// @Nested +// @DisplayName("updateProfile()은") +// class UpdateProfileTest { +// +// @Test +// @DisplayName("회원 정보를 정상적으로 수정한다.") +// void success() { +// // given +// Member member = new Member("oldName", "email@test.com", MemberType.KAKAO); +// MemberReq req = new MemberReq("newName", "OILY"); +// +// given(memberRepository.save(any(Member.class))) +// .willReturn(member); +// +// // when +// MemberRes result = memberService.updateProfile(member, req); +// +// // then +// assertThat(result.name()).isEqualTo("newName"); +// verify(memberRepository, times(1)).save(member); +// } +// } + @Nested @DisplayName("withdrawal()은") class WithdrawalTest { @@ -234,11 +383,12 @@ class WithdrawalTest { @DisplayName("회원 탈퇴를 정상적으로 수행한다.") void success() { // given - Member member = new Member("email@test.com", "nick"); + Member member = new Member("nickname", "email@test.com", MemberType.GOOGLE); // when memberService.withdrawal(member); + // then verify(memberRepository, times(1)).delete(member); } } From 4ecd10851adf7f9c012fff744ddea0b0417a936b Mon Sep 17 00:00:00 2001 From: dl-00-e8 Date: Mon, 29 Sep 2025 13:19:10 +0900 Subject: [PATCH 10/10] =?UTF-8?q?refactor:=20=EB=AF=B8=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=20import=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/hongik/triple/apimodule/member/MemberServiceTest.java | 1 - .../java/hongik/triple/domainmodule/domain/member/Member.java | 1 - 2 files changed, 2 deletions(-) diff --git a/api-module/src/test/java/hongik/triple/apimodule/member/MemberServiceTest.java b/api-module/src/test/java/hongik/triple/apimodule/member/MemberServiceTest.java index 8fadd20..53f9adb 100644 --- a/api-module/src/test/java/hongik/triple/apimodule/member/MemberServiceTest.java +++ b/api-module/src/test/java/hongik/triple/apimodule/member/MemberServiceTest.java @@ -3,7 +3,6 @@ import hongik.triple.apimodule.application.member.MemberService; import hongik.triple.apimodule.global.security.jwt.TokenProvider; import hongik.triple.apimodule.global.security.jwt.TokenDto; -import hongik.triple.commonmodule.dto.member.MemberReq; import hongik.triple.commonmodule.dto.member.MemberRes; import hongik.triple.commonmodule.enumerate.MemberType; import hongik.triple.domainmodule.domain.member.Member; diff --git a/domain-module/src/main/java/hongik/triple/domainmodule/domain/member/Member.java b/domain-module/src/main/java/hongik/triple/domainmodule/domain/member/Member.java index 072d813..1e10e62 100644 --- a/domain-module/src/main/java/hongik/triple/domainmodule/domain/member/Member.java +++ b/domain-module/src/main/java/hongik/triple/domainmodule/domain/member/Member.java @@ -1,7 +1,6 @@ package hongik.triple.domainmodule.domain.member; import hongik.triple.commonmodule.enumerate.MemberType; -import hongik.triple.commonmodule.enumerate.SkinType; import hongik.triple.domainmodule.common.BaseTimeEntity; import jakarta.persistence.*; import lombok.Getter;