From 1a22c2ea1bef8729a92444778931bb746d744dd3 Mon Sep 17 00:00:00 2001 From: manbron236 Date: Mon, 12 Jan 2026 21:05:21 +0900 Subject: [PATCH 1/7] =?UTF-8?q?[#35]=20fix=20:=20=EC=86=8C=EC=85=9C=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=9D=91=EB=8B=B5=20DTO=EC=97=90?= =?UTF-8?q?=20providerId=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/SocialLoginResponseDto.java | 48 +++++++++---------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/earseo/member/dto/response/SocialLoginResponseDto.java b/src/main/java/com/earseo/member/dto/response/SocialLoginResponseDto.java index 8c4dcd2..d1f2c71 100644 --- a/src/main/java/com/earseo/member/dto/response/SocialLoginResponseDto.java +++ b/src/main/java/com/earseo/member/dto/response/SocialLoginResponseDto.java @@ -1,45 +1,43 @@ package com.earseo.member.dto.response; import com.earseo.member.entity.Role; +import lombok.Builder; +@Builder public record SocialLoginResponseDto( Boolean isNewMember, String email, + String provider, + String nickname, + String providerId, // 기존 회원인 경우에만 값이 있음 String accessToken, String refreshToken, Long memberId, - String nickname, Role role ) { // 기존 회원용 - public static SocialLoginResponseDto existing( - LoginResponseDto loginResponseDto - ) { - return new SocialLoginResponseDto( - false, - loginResponseDto.email(), - loginResponseDto.accessToken(), - loginResponseDto.refreshToken(), - loginResponseDto.memberId(), - loginResponseDto.nickname(), - loginResponseDto.role() - ); + public static SocialLoginResponseDto existing(LoginResponseDto loginResponseDto) { + return SocialLoginResponseDto.builder() + .isNewMember(false) + .email(loginResponseDto.email()) + .nickname(loginResponseDto.nickname()) // 기존 닉네임 + .accessToken(loginResponseDto.accessToken()) + .refreshToken(loginResponseDto.refreshToken()) + .memberId(loginResponseDto.memberId()) + .role(loginResponseDto.role()) + .build(); } // 신규 회원용 응답 생성 - public static SocialLoginResponseDto newMember( - String email - ) { - return new SocialLoginResponseDto( - true, - email, - null, - null, - null, - null, - null - ); + public static SocialLoginResponseDto newMember(String email, String provider, String providerId , String nickname) { + return SocialLoginResponseDto.builder() + .isNewMember(true) + .email(email) + .provider(provider) + .providerId(providerId) + .nickname(nickname) + .build(); } } \ No newline at end of file From 5ee6f6ddedb158f801c7095006587a4f9e6bfb74 Mon Sep 17 00:00:00 2001 From: manbron236 Date: Mon, 12 Jan 2026 21:05:32 +0900 Subject: [PATCH 2/7] =?UTF-8?q?[#35]=20fix=20:=20=EC=86=8C=EC=85=9C=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=EC=9A=94=EC=B2=AD=20DTO?= =?UTF-8?q?=EC=97=90=20providerId=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/earseo/member/dto/request/SocialSignUpRequestDto.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/earseo/member/dto/request/SocialSignUpRequestDto.java b/src/main/java/com/earseo/member/dto/request/SocialSignUpRequestDto.java index 88ca770..39dcffc 100644 --- a/src/main/java/com/earseo/member/dto/request/SocialSignUpRequestDto.java +++ b/src/main/java/com/earseo/member/dto/request/SocialSignUpRequestDto.java @@ -17,6 +17,9 @@ public record SocialSignUpRequestDto( @NotNull(message = "Provider는 필수입니다.") Provider provider, + @NotBlank(message = "providerId는 필수입니다.") + String providerId, + @NotBlank(message = "닉네임은 필수입니다.") @Size(min = 2, max = 50, message = "닉네임은 2자 이상 50자 이하여야 합니다.") String nickname, From ce862e4274f7a64b4b8f36c214f455556b1ac3e2 Mon Sep 17 00:00:00 2001 From: manbron236 Date: Mon, 12 Jan 2026 21:05:48 +0900 Subject: [PATCH 3/7] =?UTF-8?q?[#35]=20fix=20:=20Apple=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20providerId?= =?UTF-8?q?=20=EC=A0=84=EB=8B=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/oauth/AppleLoginService.java | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/earseo/member/service/oauth/AppleLoginService.java b/src/main/java/com/earseo/member/service/oauth/AppleLoginService.java index 80bab51..9d7207a 100644 --- a/src/main/java/com/earseo/member/service/oauth/AppleLoginService.java +++ b/src/main/java/com/earseo/member/service/oauth/AppleLoginService.java @@ -3,6 +3,7 @@ import com.earseo.member.common.exception.BaseException; import com.earseo.member.dto.request.AppleLoginRequestDto; import com.earseo.member.dto.response.LoginResponseDto; +import com.earseo.member.dto.response.SocialLoginResponseDto; import com.earseo.member.entity.Member; import com.earseo.member.entity.Provider; import com.earseo.member.entity.Role; @@ -26,34 +27,41 @@ public class AppleLoginService { private final JwtUtil jwtUtil; @Transactional - public LoginResponseDto login(AppleLoginRequestDto request) { + public SocialLoginResponseDto login(AppleLoginRequestDto request) { Claims claims = appleTokenVerifier.verifyAndGetClaims(request.identityToken()); String providerId = claims.getSubject(); String email = claims.get("email", String.class); + // 애플은 이메일 비공개일 경우 이메일이 안 올 수 있음 -> providerId로 대체 + if (email == null) { + email = providerId + "@apple.private"; + } + // 이미 Apple로 가입한 사용자인지 확인 - Optional existingAppleMember = memberRepository + Optional existingMember = memberRepository .findByProviderAndProviderId(Provider.APPLE, providerId); - if (existingAppleMember.isPresent()) { - // 기존 Apple 사용자는 로그인 처리 - return generateLoginResponse(existingAppleMember.get()); - } + if (existingMember.isPresent()) { + Member member = existingMember.get(); - if (email != null) { - Optional existingEmailMember = memberRepository.findByEmail(email); + String accessToken = jwtUtil.generateAccessToken(member.getMemberId(), member.getEmail(), member.getRole()); + String refreshToken = jwtUtil.generateRefreshToken(member.getMemberId()); - if (existingEmailMember.isPresent() && - existingEmailMember.get().getProvider() != Provider.APPLE) { - throw new BaseException(MemberErrorCode.ALREADY_REGISTERED_WITH_DIFFERENT_PROVIDER); - } - } + LoginResponseDto loginData = new LoginResponseDto( + accessToken, refreshToken, member.getMemberId(), + member.getEmail(), member.getNickname(), member.getRole() + ); - Member newMember = createAppleMember(providerId, email, request.fullName()); - Member savedMember = memberRepository.save(newMember); + return SocialLoginResponseDto.existing(loginData); + } - return generateLoginResponse(savedMember); + return SocialLoginResponseDto.newMember( + email, + "APPLE", + providerId, + request.fullName() + ); } private Member createAppleMember(String providerId, String email, String fullName) { From a825fbe41c3a965ca019d69d3bddf5a26adf3be2 Mon Sep 17 00:00:00 2001 From: manbron236 Date: Mon, 12 Jan 2026 21:05:59 +0900 Subject: [PATCH 4/7] =?UTF-8?q?[#35]=20fix=20:=20=EC=86=8C=EC=85=9C=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20providerId=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/earseo/member/service/AuthService.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/earseo/member/service/AuthService.java b/src/main/java/com/earseo/member/service/AuthService.java index 59fa5c9..ee029d3 100644 --- a/src/main/java/com/earseo/member/service/AuthService.java +++ b/src/main/java/com/earseo/member/service/AuthService.java @@ -143,7 +143,12 @@ public SocialLoginResponseDto googleLogin(String code) { return SocialLoginResponseDto.existing(loginResponse); } else { // 신규 회원 - 추가 정보 입력 필요 - return SocialLoginResponseDto.newMember(googleUser.email()); + return SocialLoginResponseDto.newMember( + googleUser.email(), + "GOOGLE", + googleUser.id(), // Google의 providerId (sub 클레임) + googleUser.name() // 또는 null + ); } } @@ -161,6 +166,7 @@ public LoginResponseDto completeSocialSignUp(SocialSignUpRequestDto request) { Member member = Member.builder() .email(request.email()) .provider(request.provider()) + .providerId(request.providerId()) .nickname(request.nickname()) .gender(request.gender()) .birthdate(request.birthdate()) From de181d1c970d0ab345aea7de8db54011f13bead2 Mon Sep 17 00:00:00 2001 From: manbron236 Date: Mon, 12 Jan 2026 21:06:38 +0900 Subject: [PATCH 5/7] =?UTF-8?q?[#35]=20fix=20:=20SocialLoginResponseDto?= =?UTF-8?q?=EB=A1=9C=20DTO=20=EC=A7=80=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 --- .../java/com/earseo/member/controller/AuthController.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/earseo/member/controller/AuthController.java b/src/main/java/com/earseo/member/controller/AuthController.java index 95375c8..cf8c21f 100644 --- a/src/main/java/com/earseo/member/controller/AuthController.java +++ b/src/main/java/com/earseo/member/controller/AuthController.java @@ -40,7 +40,6 @@ public ResponseEntity> googleCallback( return ResponseEntity.ok(BaseResponse.ok(response)); } - @Operation(summary = "소셜 로그인 추가 정보 입력", description = "신규 소셜 회원 추가 정보 입력 및 회원가입 완료") @PostMapping("/oauth/additional-info") public ResponseEntity> completeSocialSignUp( @@ -104,9 +103,9 @@ public ResponseEntity> resetPassword( @Operation(summary = "애플 로그인", description = "Apple identityToken으로 로그인/회원가입 처리") @PostMapping("/oauth/apple") - public ResponseEntity> appleLogin( + public ResponseEntity> appleLogin( @RequestBody AppleLoginRequestDto request) { - LoginResponseDto response = appleLoginService.login(request); + SocialLoginResponseDto response = appleLoginService.login(request); return ResponseEntity.ok(BaseResponse.ok(response)); } } From 9fbcc26cf13736b3bd05b287854f5062e9fda655 Mon Sep 17 00:00:00 2001 From: manbron236 Date: Mon, 12 Jan 2026 21:06:57 +0900 Subject: [PATCH 6/7] [#35] fix : helm value --- k8s/helm-value.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k8s/helm-value.yaml b/k8s/helm-value.yaml index d16af7a..a2cf555 100644 --- a/k8s/helm-value.yaml +++ b/k8s/helm-value.yaml @@ -1,5 +1,5 @@ image: - tag: v0.1.12 + tag: v0.1.13 env: JWT_ACCESS_TOKEN_EXPIRATION: "1800000" JWT_REFRESH_TOKEN_EXPIRATION: "1209600000" \ No newline at end of file From 5c87b7498e3f7d139c5f2978d886115dd86ca7cc Mon Sep 17 00:00:00 2001 From: manbron236 Date: Tue, 13 Jan 2026 12:48:40 +0900 Subject: [PATCH 7/7] =?UTF-8?q?[#35]=20fix=20:=20=EC=9E=84=EC=8B=9C=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=EB=B0=9C=EA=B8=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/exception/MemberErrorCode.java | 3 +- .../dto/request/SocialSignUpRequestDto.java | 4 +- .../dto/response/SocialLoginResponseDto.java | 6 +-- .../earseo/member/service/AuthService.java | 14 +++++- .../service/oauth/AppleLoginService.java | 50 ++----------------- .../oauth/SocialSignUpTempService.java | 31 ++++++++++++ 6 files changed, 55 insertions(+), 53 deletions(-) create mode 100644 src/main/java/com/earseo/member/service/oauth/SocialSignUpTempService.java diff --git a/src/main/java/com/earseo/member/common/exception/MemberErrorCode.java b/src/main/java/com/earseo/member/common/exception/MemberErrorCode.java index 80a4741..c140d02 100644 --- a/src/main/java/com/earseo/member/common/exception/MemberErrorCode.java +++ b/src/main/java/com/earseo/member/common/exception/MemberErrorCode.java @@ -27,7 +27,8 @@ public enum MemberErrorCode implements ErrorCodeInterface { APPLE_TOKEN_INVALID("MEM015", "유효하지 않은 Apple 토큰입니다.", HttpStatus.UNAUTHORIZED), APPLE_PUBLIC_KEY_NOT_FOUND("MEM016", "Apple 공개키를 찾을 수 없습니다.", HttpStatus.INTERNAL_SERVER_ERROR), APPLE_TOKEN_EXPIRED("MEM017", "만료된 Apple 토큰입니다.", HttpStatus.UNAUTHORIZED), - APPLE_SERVER_ERROR("MEM018", "Apple 서버 연동 중 오류가 발생했습니다.", HttpStatus.SERVICE_UNAVAILABLE); + APPLE_SERVER_ERROR("MEM018", "Apple 서버 연동 중 오류가 발생했습니다.", HttpStatus.SERVICE_UNAVAILABLE), + INVALID_TEMP_TOKEN("MEM019", "유효하지 않거나 만료된 토큰입니다.", HttpStatus.BAD_REQUEST); private final String status; private final String message; diff --git a/src/main/java/com/earseo/member/dto/request/SocialSignUpRequestDto.java b/src/main/java/com/earseo/member/dto/request/SocialSignUpRequestDto.java index 39dcffc..1422d9b 100644 --- a/src/main/java/com/earseo/member/dto/request/SocialSignUpRequestDto.java +++ b/src/main/java/com/earseo/member/dto/request/SocialSignUpRequestDto.java @@ -17,8 +17,8 @@ public record SocialSignUpRequestDto( @NotNull(message = "Provider는 필수입니다.") Provider provider, - @NotBlank(message = "providerId는 필수입니다.") - String providerId, + @NotBlank(message = "임시 토큰은 필수입니다.") + String tempToken, @NotBlank(message = "닉네임은 필수입니다.") @Size(min = 2, max = 50, message = "닉네임은 2자 이상 50자 이하여야 합니다.") diff --git a/src/main/java/com/earseo/member/dto/response/SocialLoginResponseDto.java b/src/main/java/com/earseo/member/dto/response/SocialLoginResponseDto.java index d1f2c71..abafcfb 100644 --- a/src/main/java/com/earseo/member/dto/response/SocialLoginResponseDto.java +++ b/src/main/java/com/earseo/member/dto/response/SocialLoginResponseDto.java @@ -9,7 +9,7 @@ public record SocialLoginResponseDto( String email, String provider, String nickname, - String providerId, + String tempToken, // 기존 회원인 경우에만 값이 있음 String accessToken, @@ -31,12 +31,12 @@ public static SocialLoginResponseDto existing(LoginResponseDto loginResponseDto) } // 신규 회원용 응답 생성 - public static SocialLoginResponseDto newMember(String email, String provider, String providerId , String nickname) { + public static SocialLoginResponseDto newMember(String email, String provider, String tempToken , String nickname) { return SocialLoginResponseDto.builder() .isNewMember(true) .email(email) .provider(provider) - .providerId(providerId) + .tempToken(tempToken) .nickname(nickname) .build(); } diff --git a/src/main/java/com/earseo/member/service/AuthService.java b/src/main/java/com/earseo/member/service/AuthService.java index ee029d3..fb788da 100644 --- a/src/main/java/com/earseo/member/service/AuthService.java +++ b/src/main/java/com/earseo/member/service/AuthService.java @@ -9,6 +9,7 @@ import com.earseo.member.entity.Role; import com.earseo.member.repository.MemberRepository; import com.earseo.member.service.oauth.GoogleOAuthService; +import com.earseo.member.service.oauth.SocialSignUpTempService; import com.earseo.member.util.JwtUtil; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; @@ -29,6 +30,7 @@ public class AuthService { private final RefreshTokenService refreshTokenService; private final EmailService emailService; private final EmailVerificationService emailVerificationService; + private final SocialSignUpTempService socialSignUpTempService; @Value("${member.default-profile-image}") private String defaultProfileImage; @@ -155,6 +157,13 @@ public SocialLoginResponseDto googleLogin(String code) { @Transactional public LoginResponseDto completeSocialSignUp(SocialSignUpRequestDto request) { + + // tempToken으로 providerId 조회 + String providerId = socialSignUpTempService.getProviderId(request.tempToken()); + if (providerId == null) { + throw new BaseException(MemberErrorCode.INVALID_TEMP_TOKEN); + } + // 이미 가입된 회원인지 확인 validateDuplicateMember(request.email(), request.provider()); @@ -166,7 +175,7 @@ public LoginResponseDto completeSocialSignUp(SocialSignUpRequestDto request) { Member member = Member.builder() .email(request.email()) .provider(request.provider()) - .providerId(request.providerId()) + .providerId(providerId) .nickname(request.nickname()) .gender(request.gender()) .birthdate(request.birthdate()) @@ -177,6 +186,9 @@ public LoginResponseDto completeSocialSignUp(SocialSignUpRequestDto request) { Member savedMember = memberRepository.save(member); + // tempToken 삭제 + socialSignUpTempService.deleteTempToken(request.tempToken()); + String accessToken = jwtUtil.generateAccessToken( savedMember.getMemberId(), savedMember.getEmail(), diff --git a/src/main/java/com/earseo/member/service/oauth/AppleLoginService.java b/src/main/java/com/earseo/member/service/oauth/AppleLoginService.java index 9d7207a..ce08f40 100644 --- a/src/main/java/com/earseo/member/service/oauth/AppleLoginService.java +++ b/src/main/java/com/earseo/member/service/oauth/AppleLoginService.java @@ -1,13 +1,10 @@ package com.earseo.member.service.oauth; -import com.earseo.member.common.exception.BaseException; import com.earseo.member.dto.request.AppleLoginRequestDto; import com.earseo.member.dto.response.LoginResponseDto; import com.earseo.member.dto.response.SocialLoginResponseDto; import com.earseo.member.entity.Member; import com.earseo.member.entity.Provider; -import com.earseo.member.entity.Role; -import com.earseo.member.common.exception.MemberErrorCode; import com.earseo.member.repository.MemberRepository; import com.earseo.member.util.JwtUtil; import io.jsonwebtoken.Claims; @@ -25,6 +22,7 @@ public class AppleLoginService { private final AppleTokenVerifier appleTokenVerifier; private final MemberRepository memberRepository; private final JwtUtil jwtUtil; + private final SocialSignUpTempService socialSignUpTempService; @Transactional public SocialLoginResponseDto login(AppleLoginRequestDto request) { @@ -56,53 +54,13 @@ public SocialLoginResponseDto login(AppleLoginRequestDto request) { return SocialLoginResponseDto.existing(loginData); } + String tempToken = socialSignUpTempService.createTempToken(providerId); + return SocialLoginResponseDto.newMember( email, "APPLE", - providerId, + tempToken, request.fullName() ); } - - private Member createAppleMember(String providerId, String email, String fullName) { - String memberEmail = email != null ? email : providerId + "@apple.private"; - - // 닉네임 중복 방지를 위해 항상 UUID 붙이기 - String baseNickname = (fullName != null && !fullName.isBlank()) - ? fullName - : "User"; - String nickname = baseNickname + "_" + UUID.randomUUID().toString().substring(0, 8); - - // 혹시 중복이면 다시 생성 - while (memberRepository.existsByNickname(nickname)) { - nickname = baseNickname + "_" + UUID.randomUUID().toString().substring(0, 8); - } - - return Member.builder() - .email(memberEmail) - .provider(Provider.APPLE) - .providerId(providerId) - .nickname(nickname) - .role(Role.USER) - .password(null) - .build(); - } - - private LoginResponseDto generateLoginResponse(Member member) { - String accessToken = jwtUtil.generateAccessToken( - member.getMemberId(), - member.getEmail(), - member.getRole() - ); - String refreshToken = jwtUtil.generateRefreshToken(member.getMemberId()); - - return new LoginResponseDto( - accessToken, - refreshToken, - member.getMemberId(), - member.getEmail(), - member.getNickname(), - member.getRole() - ); - } } \ No newline at end of file diff --git a/src/main/java/com/earseo/member/service/oauth/SocialSignUpTempService.java b/src/main/java/com/earseo/member/service/oauth/SocialSignUpTempService.java new file mode 100644 index 0000000..1ca5fe6 --- /dev/null +++ b/src/main/java/com/earseo/member/service/oauth/SocialSignUpTempService.java @@ -0,0 +1,31 @@ +package com.earseo.member.service.oauth; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; + +import java.time.Duration; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class SocialSignUpTempService { + + private final StringRedisTemplate stringRedisTemplate; + private static final String PREFIX = "social:signup:"; + private static final Duration TTL = Duration.ofMinutes(30); + + public String createTempToken(String providerId) { + String tempToken = UUID.randomUUID().toString(); + stringRedisTemplate.opsForValue().set(PREFIX + tempToken, providerId, TTL); + return tempToken; + } + + public String getProviderId(String tempToken) { + return stringRedisTemplate.opsForValue().get(PREFIX + tempToken); + } + + public void deleteTempToken(String tempToken) { + stringRedisTemplate.delete(PREFIX + tempToken); + } +}