From adf99b75f26d855ebbaa3818b81d68c956e7906c Mon Sep 17 00:00:00 2001 From: issuejong Date: Mon, 17 Aug 2026 20:17:48 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20Apple=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20id?= =?UTF-8?q?entityToken=20=EC=9A=94=EC=B2=AD=20=ED=98=B8=ED=99=98=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/AuthController.java | 4 +- .../domain/member/dto/AuthRequestDTO.java | 4 +- .../member/controller/AuthControllerTest.java | 103 ++++++++++++++++++ 3 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 src/test/java/com/moru/server/domain/member/controller/AuthControllerTest.java diff --git a/src/main/java/com/moru/server/domain/member/controller/AuthController.java b/src/main/java/com/moru/server/domain/member/controller/AuthController.java index 7ed590c..2675ab7 100644 --- a/src/main/java/com/moru/server/domain/member/controller/AuthController.java +++ b/src/main/java/com/moru/server/domain/member/controller/AuthController.java @@ -39,7 +39,7 @@ public class AuthController { - Authorization 헤더 없이 호출합니다. - kakao: token에 카카오 accessToken을 전달합니다. - google: token에 Google idToken을 전달합니다. - - apple: token에 Apple identityToken을 전달합니다. + - apple: identityToken에 Apple identityToken을 전달합니다. token도 호환됩니다. """; private static final String KAKAO_REQUEST_EXAMPLE = """ { @@ -55,7 +55,7 @@ public class AuthController { """; private static final String APPLE_REQUEST_EXAMPLE = """ { - "token": "apple_identity_token", + "identityToken": "apple_identity_token", "authorizationCode": "apple_authorization_code" } """; diff --git a/src/main/java/com/moru/server/domain/member/dto/AuthRequestDTO.java b/src/main/java/com/moru/server/domain/member/dto/AuthRequestDTO.java index 257eb03..cd6e6b9 100644 --- a/src/main/java/com/moru/server/domain/member/dto/AuthRequestDTO.java +++ b/src/main/java/com/moru/server/domain/member/dto/AuthRequestDTO.java @@ -1,5 +1,6 @@ package com.moru.server.domain.member.dto; +import com.fasterxml.jackson.annotation.JsonAlias; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -8,7 +9,8 @@ public record AuthRequestDTO() { @Schema(description = "소셜 로그인 요청") public record SocialLoginRequest( @NotBlank(message = "소셜 토큰은 필수입니다.") - @Schema(description = "소셜 플랫폼에서 발급받은 토큰. kakao는 accessToken, google은 idToken, apple은 identityToken을 전달합니다.", example = "kakao_access_token") + @JsonAlias("identityToken") + @Schema(description = "소셜 플랫폼에서 발급받은 토큰. kakao는 accessToken, google은 idToken, apple은 token 또는 identityToken을 전달합니다.", example = "kakao_access_token") String token, @Schema(description = "Apple 로그인 필수 인가 코드. kakao/google은 null로 전달합니다.", example = "apple_authorization_code") diff --git a/src/test/java/com/moru/server/domain/member/controller/AuthControllerTest.java b/src/test/java/com/moru/server/domain/member/controller/AuthControllerTest.java new file mode 100644 index 0000000..9b1e374 --- /dev/null +++ b/src/test/java/com/moru/server/domain/member/controller/AuthControllerTest.java @@ -0,0 +1,103 @@ +package com.moru.server.domain.member.controller; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import com.moru.server.domain.member.dto.AuthRequestDTO; +import com.moru.server.domain.member.dto.AuthResponseDTO; +import com.moru.server.domain.member.entity.enums.OAuthProvider; +import com.moru.server.domain.member.service.command.auth.AuthCommandService; + +@ActiveProfiles("test") +@SpringBootTest +@AutoConfigureMockMvc +class AuthControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private AuthCommandService authCommandService; + + @Test + void acceptsIdentityTokenForAppleLogin() throws Exception { + AuthRequestDTO.SocialLoginRequest request = + new AuthRequestDTO.SocialLoginRequest("apple-identity-token", "apple-authorization-code"); + when(authCommandService.loginWithSocial(OAuthProvider.APPLE, request)) + .thenReturn(socialLoginResponse()); + + mockMvc.perform(post("/auth/login/apple") + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "identityToken": "apple-identity-token", + "authorizationCode": "apple-authorization-code" + } + """)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.isSuccess").value(true)) + .andExpect(jsonPath("$.result.memberId").value(1)); + + verify(authCommandService).loginWithSocial(OAuthProvider.APPLE, request); + } + + @Test + void keepsSupportingTokenForAppleLogin() throws Exception { + AuthRequestDTO.SocialLoginRequest request = + new AuthRequestDTO.SocialLoginRequest("apple-identity-token", "apple-authorization-code"); + when(authCommandService.loginWithSocial(OAuthProvider.APPLE, request)) + .thenReturn(socialLoginResponse()); + + mockMvc.perform(post("/auth/login/apple") + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "token": "apple-identity-token", + "authorizationCode": "apple-authorization-code" + } + """)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.isSuccess").value(true)); + + verify(authCommandService).loginWithSocial(OAuthProvider.APPLE, request); + } + + @Test + void rejectsAppleLoginWithoutSocialToken() throws Exception { + mockMvc.perform(post("/auth/login/apple") + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "authorizationCode": "apple-authorization-code" + } + """)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.isSuccess").value(false)) + .andExpect(jsonPath("$.code").value("COMMON400")); + + verifyNoInteractions(authCommandService); + } + + private AuthResponseDTO.SocialLoginResponse socialLoginResponse() { + return AuthResponseDTO.SocialLoginResponse.builder() + .memberId(1L) + .accessToken("access-token") + .refreshToken("refresh-token") + .isNewMember(true) + .onboardingCompleted(false) + .build(); + } +}