Skip to content

Commit

Permalink
Refactor: OauthService.revokeOauth 메서드에서 revoke 호출을 가상 스레드를 이용한 비동기 호…
Browse files Browse the repository at this point in the history
…출로 변경 (#227)

* Refactor: OauthService.revokeOauth 메서드에서 revoke 호출을 가상 스레드를 이용한 비동기 호출로 변경

* Test: OauthServiceTest에 비동기 변경사항 반영
  • Loading branch information
Jaewon-pro authored Sep 25, 2024
1 parent c3b9a1f commit 19acb06
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 41 deletions.
44 changes: 22 additions & 22 deletions src/main/java/com/dnd/runus/application/oauth/OauthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,30 +92,30 @@ public void revokeOauth(long memberId, WithdrawRequest request) {

OidcProvider oidcProvider = oidcProviderRegistry.getOidcProviderBy(request.socialType());

Claims claim = oidcProvider.getClaimsBy(request.idToken());
String oauthId = claim.getSubject();
String oauthId = oidcProvider.getClaimsBy(request.idToken()).getSubject();

SocialProfile socialProfile = socialProfileRepository
socialProfileRepository
.findBySocialTypeAndOauthId(request.socialType(), oauthId)
.orElseThrow(() -> {
log.error(
"data conflict: 존재하지 않은 SocialProfile입니다. socialType: {}, oauthId: {}",
request.socialType(),
oauthId);
return new NotFoundException("존재하지 않은 SocialProfile");
});

if (memberId != socialProfile.member().memberId()) {
log.error(
"DATA CONFLICT: MemberId and MemberId find by SocialProfile oauthId do not match each other! memberId: {}, socialProfileId: {}",
memberId,
socialProfile.socialProfileId());
throw new NotFoundException("잘못된 데이터: Member and SocialProfile do not match each other");
}

// 탈퇴를 위한 access token 발급
String accessToken = oidcProvider.getAccessToken(request.authorizationCode());
oidcProvider.revoke(accessToken);
.filter(profile -> profile.member().memberId() == memberId)
.orElseThrow(() -> new AuthException(
ErrorType.INVALID_CREDENTIALS,
String.format(
"socialType: %s, oauthId: %s, memberId: %s", request.socialType(), oauthId, memberId)));

Thread.startVirtualThread(() -> {
try {
String accessToken = oidcProvider.getAccessToken(request.authorizationCode());
oidcProvider.revoke(accessToken);
log.info("토큰 revoke 성공. memberId: {}, socialType: {}", memberId, request.socialType());
} catch (Exception e) {
log.warn(
"토큰 revoke 실패. memberId: {}, socialType: {}, {}",
memberId,
request.socialType(),
e.getMessage());
}
});
log.info("토큰 revoke 요청 완료. memberId: {}, socialType: {}", memberId, request.socialType());
}

@Transactional
Expand Down
25 changes: 21 additions & 4 deletions src/main/java/com/dnd/runus/auth/oidc/client/RestClientConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,38 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.JdkClientHttpRequestFactory;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.support.RestClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;

import java.net.http.HttpClient;
import java.time.Duration;
import java.util.concurrent.Executors;

@Configuration
public class RestClientConfig {

@Value("${spring.threads.virtual.enabled}")
private boolean isVirtualThreadEnabled;

private static final Duration READ_TIMEOUT = Duration.ofSeconds(1);
private static final Duration CONNECT_TIMEOUT = Duration.ofSeconds(3);

@Bean
AppleAuthClient appleAuthClient(@Value("${oauth.apple.base-auth-url}") String baseAuthUrl) {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS
.withReadTimeout(Duration.ofSeconds(5))
.withConnectTimeout(Duration.ofSeconds(10));
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(settings);
ClientHttpRequestFactory requestFactory;

if (isVirtualThreadEnabled) {
requestFactory = new JdkClientHttpRequestFactory(HttpClient.newBuilder()
.connectTimeout(CONNECT_TIMEOUT)
.executor(Executors.newVirtualThreadPerTaskExecutor())
.build());
} else {
requestFactory = ClientHttpRequestFactories.get(ClientHttpRequestFactorySettings.DEFAULTS
.withReadTimeout(READ_TIMEOUT)
.withConnectTimeout(CONNECT_TIMEOUT));
}

RestClient restClient = RestClient.builder()
.baseUrl(baseAuthUrl)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.dnd.runus.auth.oidc.provider.apple;

import com.dnd.runus.auth.exception.AuthException;
import com.dnd.runus.auth.oidc.client.AppleAuthClient;
import com.dnd.runus.auth.oidc.provider.OidcProvider;
import com.dnd.runus.auth.oidc.provider.apple.dto.AppleAuthRevokeRequest;
Expand Down Expand Up @@ -77,7 +78,7 @@ public String getAccessToken(String code) {
.build();
return appleAuthClient.getAuthToken(request.toMultiValueMap()).accessToken();
} catch (HttpClientErrorException e) {
throw new BusinessException(ErrorType.FAILED_AUTHENTICATION, e.getMessage());
throw new AuthException(ErrorType.FAILED_AUTHENTICATION, e.getMessage());
}
}

Expand All @@ -92,8 +93,7 @@ public void revoke(String accessToken) {
.build();
appleAuthClient.revoke(request.toMultiValueMap());
} catch (HttpClientErrorException e) {
log.warn("failed token revoke :{}", e.getMessage());
throw new BusinessException(ErrorType.FAILED_AUTHENTICATION, e.getMessage());
throw new AuthException(ErrorType.FAILED_AUTHENTICATION, e.getMessage());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public enum ErrorType {
TAMPERED_ACCESS_TOKEN(UNAUTHORIZED, "AUTH_005", "변조된 토큰입니다"),
UNSUPPORTED_JWT_TOKEN(UNAUTHORIZED, "AUTH_006", "지원하지 않는 JWT 토큰입니다"),
UNSUPPORTED_SOCIAL_TYPE(UNAUTHORIZED, "AUTH_007", "지원하지 않는 소셜 타입입니다."),
INVALID_CREDENTIALS(UNAUTHORIZED, "AUTH_008", "해당 사용자의 정보가 없거나 일치하지 않아 처리할 수 없습니다."),

// OauthErrorType
USER_NOT_FOUND(NOT_FOUND, "OAUTH_001", "존재하지 않은 사용자 입니다."),
Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ spring:
enabled: true
baseline-on-migrate: true

threads:
virtual:
enabled: false

app:
api:
allow-origins: ${ALLOW_ORIGINS}
Expand Down
36 changes: 24 additions & 12 deletions src/test/java/com/dnd/runus/application/oauth/OauthServiceTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.dnd.runus.application.oauth;

import com.dnd.runus.auth.exception.AuthException;
import com.dnd.runus.auth.oidc.provider.OidcProvider;
import com.dnd.runus.auth.oidc.provider.OidcProviderRegistry;
import com.dnd.runus.auth.token.TokenProviderModule;
Expand All @@ -10,11 +11,7 @@
import com.dnd.runus.domain.common.Coordinate;
import com.dnd.runus.domain.common.Pace;
import com.dnd.runus.domain.goalAchievement.GoalAchievementRepository;
import com.dnd.runus.domain.member.Member;
import com.dnd.runus.domain.member.MemberLevelRepository;
import com.dnd.runus.domain.member.MemberRepository;
import com.dnd.runus.domain.member.SocialProfile;
import com.dnd.runus.domain.member.SocialProfileRepository;
import com.dnd.runus.domain.member.*;
import com.dnd.runus.domain.running.RunningRecord;
import com.dnd.runus.domain.running.RunningRecordRepository;
import com.dnd.runus.domain.scale.ScaleAchievementRepository;
Expand Down Expand Up @@ -43,7 +40,9 @@
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;

import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.BDDMockito.*;

Expand Down Expand Up @@ -220,7 +219,7 @@ void socialProfile_not_exist_then_signUp_save_social_profile() {

@DisplayName("소셜 로그인 연동 해제: 성공")
@Test
void revokeOauth_Success() {
void revokeOauth_Success() throws InterruptedException {

// given
WithdrawRequest request = new WithdrawRequest(socialType, authorizationCode, idToken);
Expand All @@ -233,14 +232,27 @@ void revokeOauth_Success() {
given(socialProfileRepository.findBySocialTypeAndOauthId(request.socialType(), oauthId))
.willReturn(Optional.of(socialProfileMock));

CountDownLatch latch = new CountDownLatch(2);

String accessToken = "accessToken";
given(oidcProvider.getAccessToken(request.authorizationCode())).willReturn(accessToken);
given(oidcProvider.getAccessToken(request.authorizationCode())).willAnswer(invocation -> {
latch.countDown();
return accessToken;
});

will(invocation -> {
latch.countDown();
return null;
})
.given(oidcProvider)
.revoke(accessToken);

// when
oauthService.revokeOauth(member.memberId(), request);

// then
then(oidcProvider).should().revoke(accessToken);
boolean completed = latch.await(100, MILLISECONDS);
assertTrue(completed);
}

@DisplayName("소셜 로그인 연동 해제: member_id가 없을 경우 NotFoundException을 발생한다.")
Expand All @@ -254,7 +266,7 @@ void revokeOauth_NotFound_MemberID() {
assertThrows(NotFoundException.class, () -> oauthService.revokeOauth(member.memberId(), request));
}

@DisplayName("소셜 로그인 연동 해제: oauthId와 socialType에 해당하는 socialProfile 없을 경우 NotFoundException을 발생한다.")
@DisplayName("소셜 로그인 연동 해제: oauthId와 socialType에 해당하는 socialProfile 없을 경우 AuthException을 발생한다.")
@Test
void revokeOauth_NotFound_SocialProfile() {
// given
Expand All @@ -267,10 +279,10 @@ void revokeOauth_NotFound_SocialProfile() {
.willReturn(Optional.empty());

// when, then
assertThrows(NotFoundException.class, () -> oauthService.revokeOauth(member.memberId(), request));
assertThrows(AuthException.class, () -> oauthService.revokeOauth(member.memberId(), request));
}

@DisplayName("소셜 로그인 연동 해제: socialProfile의 memberId와 member의 id가 다를 경우 NotFoundException을 발생한다.")
@DisplayName("소셜 로그인 연동 해제: socialProfile의 memberId와 member의 id가 다를 경우 AuthException을 발생한다.")
@Test
void revokeOauth_MissMatch_socialProfileAndMemberId() {
// given
Expand All @@ -290,7 +302,7 @@ void revokeOauth_MissMatch_socialProfileAndMemberId() {
.willReturn(Optional.of(socialProfileMock));

// when, then
assertThrows(NotFoundException.class, () -> oauthService.revokeOauth(member.memberId(), request));
assertThrows(AuthException.class, () -> oauthService.revokeOauth(member.memberId(), request));
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/test/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ spring:
enabled: true
baseline-on-migrate: true # Baseline 생성이 필요한 상황에서 migration 작업 실행시, Baseline 생성부터 하겠다는 설정

threads:
virtual:
enabled: false

app:
api:
allow-origins: "*"
Expand Down

0 comments on commit 19acb06

Please sign in to comment.