Skip to content

Commit 0633d6a

Browse files
committed
fix: allow Apple login without token exchange
1 parent 486e3a3 commit 0633d6a

4 files changed

Lines changed: 76 additions & 6 deletions

File tree

ontime-back/src/main/java/devkor/ontime_back/global/oauth/apple/AppleLoginFilter.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,7 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ
8181
}
8282

8383
String appleUserId = tokenClaims.getSubject();
84-
String email = tokenClaims.get("email", String.class);
85-
86-
// socialRefreshtoken에 저장
87-
String appleRefreshToken = appleLoginService.getAppleAccessTokenAndRefreshToken(oAuthAppleRequestDto.getAuthCode()).getRefreshToken();
84+
String appleRefreshToken = exchangeAppleRefreshToken(oAuthAppleRequestDto);
8885

8986
OAuthAppleUserDto oAuthAppleUserDto = new OAuthAppleUserDto(appleUserId, oAuthAppleRequestDto.getEmail(), oAuthAppleRequestDto.getFullName());
9087

@@ -102,6 +99,18 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ
10299
}
103100
}
104101

102+
private String exchangeAppleRefreshToken(OAuthAppleRequestDto oAuthAppleRequestDto) {
103+
try {
104+
AppleTokenResponseDto tokenResponse = appleLoginService.getAppleAccessTokenAndRefreshToken(
105+
oAuthAppleRequestDto.getAuthCode()
106+
);
107+
return tokenResponse.getRefreshToken();
108+
} catch (Exception e) {
109+
log.warn("Apple token exchange failed; continuing with verified identity token", e);
110+
return null;
111+
}
112+
}
113+
105114
@Override
106115
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
107116
AuthenticationException failed) throws IOException {

ontime-back/src/main/java/devkor/ontime_back/global/oauth/apple/AppleLoginService.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ public class AppleLoginService {
7474
private final RestTemplate restTemplate = new RestTemplate();
7575
public Authentication handleLogin(String appleRefreshToken, User user, HttpServletResponse response) throws IOException {
7676
log.info("handleLogin");
77-
user.updateSocialLoginToken(appleRefreshToken);
77+
if (appleRefreshToken != null && !appleRefreshToken.isBlank()) {
78+
user.updateSocialLoginToken(appleRefreshToken);
79+
}
7880

7981
String accessToken = jwtTokenProvider.createAccessToken(user.getEmail(), user.getId());
8082
String refreshToken = jwtTokenProvider.createRefreshToken();

ontime-back/src/test/java/devkor/ontime_back/global/oauth/OAuthLoginFilterValidationTest.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.util.Optional;
3535

3636
import static org.mockito.ArgumentMatchers.any;
37+
import static org.mockito.ArgumentMatchers.eq;
3738
import static org.assertj.core.api.Assertions.assertThat;
3839
import static org.assertj.core.api.Assertions.assertThatThrownBy;
3940
import static org.mockito.Mockito.times;
@@ -341,6 +342,35 @@ void appleLoginFilterLogsInExistingUser() throws Exception {
341342
verify(appleLoginService).handleLogin("apple-refresh-token", existingUser, response);
342343
}
343344

345+
@Test
346+
@DisplayName("애플 로그인 필터가 Apple refresh token 교환 실패 시 identity token 검증만으로 기존 유저를 로그인 처리한다")
347+
void appleLoginFilterLogsInExistingUserWhenTokenExchangeFails() throws Exception {
348+
AppleLoginFilter filter = new AppleLoginFilter(
349+
"/oauth2/apple/login",
350+
objectMapper,
351+
validator,
352+
appleLoginService,
353+
userRepository);
354+
MockHttpServletResponse response = new MockHttpServletResponse();
355+
Claims claims = Jwts.claims().setSubject("apple-id");
356+
User existingUser = user(1L, "user@example.com", Role.USER);
357+
358+
when(appleLoginService.verifyIdentityToken("apple-id-token")).thenReturn(claims);
359+
when(appleLoginService.getAppleAccessTokenAndRefreshToken("auth-code"))
360+
.thenThrow(new RuntimeException("invalid_client"));
361+
when(userRepository.findBySocialTypeAndSocialId(SocialType.APPLE, "apple-id"))
362+
.thenReturn(Optional.of(existingUser));
363+
when(appleLoginService.handleLogin(null, existingUser, response)).thenReturn(
364+
new org.springframework.security.authentication.UsernamePasswordAuthenticationToken(existingUser, null)
365+
);
366+
367+
assertThat(filter.attemptAuthentication(
368+
request("/oauth2/apple/login", validAppleBody()),
369+
response).getPrincipal()).isSameAs(existingUser);
370+
371+
verify(appleLoginService).handleLogin(null, existingUser, response);
372+
}
373+
344374
@Test
345375
@DisplayName("애플 로그인 필터가 신규 유저를 Apple identity token과 auth code로 회원가입 처리한다")
346376
void appleLoginFilterRegistersNewUser() throws Exception {
@@ -370,6 +400,35 @@ void appleLoginFilterRegistersNewUser() throws Exception {
370400
verify(appleLoginService).handleRegister(any(), any(), any());
371401
}
372402

403+
@Test
404+
@DisplayName("애플 로그인 필터가 Apple refresh token 교환 실패 시 identity token 검증만으로 신규 유저를 회원가입 처리한다")
405+
void appleLoginFilterRegistersNewUserWhenTokenExchangeFails() throws Exception {
406+
AppleLoginFilter filter = new AppleLoginFilter(
407+
"/oauth2/apple/login",
408+
objectMapper,
409+
validator,
410+
appleLoginService,
411+
userRepository);
412+
MockHttpServletResponse response = new MockHttpServletResponse();
413+
Claims claims = Jwts.claims().setSubject("apple-id");
414+
User newUser = user(2L, "new@example.com", Role.GUEST);
415+
416+
when(appleLoginService.verifyIdentityToken("apple-id-token")).thenReturn(claims);
417+
when(appleLoginService.getAppleAccessTokenAndRefreshToken("auth-code"))
418+
.thenThrow(new RuntimeException("invalid_client"));
419+
when(userRepository.findBySocialTypeAndSocialId(SocialType.APPLE, "apple-id"))
420+
.thenReturn(Optional.empty());
421+
when(appleLoginService.handleRegister(eq(null), any(), any())).thenReturn(
422+
new org.springframework.security.authentication.UsernamePasswordAuthenticationToken(newUser, null)
423+
);
424+
425+
assertThat(filter.attemptAuthentication(
426+
request("/oauth2/apple/login", validAppleBody()),
427+
response).getPrincipal()).isSameAs(newUser);
428+
429+
verify(appleLoginService).handleRegister(eq(null), any(), any());
430+
}
431+
373432
private KakaoLoginFilter kakaoLoginFilter() {
374433
return new KakaoLoginFilter(
375434
"/oauth2/kakao/login",

ontime-back/src/test/java/devkor/ontime_back/global/oauth/apple/AppleLoginFilterAuthenticationResultTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ void unsuccessfulAuthenticationWritesUnauthorizedEnvelope() throws Exception {
2929
filter.callUnsuccessfulAuthentication(new MockHttpServletRequest(), response, new BadCredentialsException("bad"));
3030

3131
assertThat(response.getStatus()).isEqualTo(401);
32-
assertThat(response.getContentAsString()).contains("Authentication failed");
32+
assertThat(response.getContentAsString()).contains("Apple 로그인 실패");
3333
}
3434

3535
private static class TestableAppleLoginFilter extends AppleLoginFilter {

0 commit comments

Comments
 (0)