diff --git a/backend/build.gradle b/backend/build.gradle index 3dbd51f0d..b4b83601a 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -43,6 +43,7 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.rest-assured:rest-assured:5.3.1' + testImplementation 'org.awaitility:awaitility:4.2.0' //log to slack implementation 'com.github.maricn:logback-slack-appender:1.4.0' diff --git a/backend/src/main/java/shook/shook/auth/application/AuthService.java b/backend/src/main/java/shook/shook/auth/application/AuthService.java index ef25d06c4..39ffac45c 100644 --- a/backend/src/main/java/shook/shook/auth/application/AuthService.java +++ b/backend/src/main/java/shook/shook/auth/application/AuthService.java @@ -7,9 +7,9 @@ import shook.shook.auth.application.dto.GoogleMemberInfoResponse; import shook.shook.auth.application.dto.ReissueAccessTokenResponse; import shook.shook.auth.application.dto.TokenPair; +import shook.shook.auth.repository.InMemoryTokenPairRepository; import shook.shook.member.application.MemberService; import shook.shook.member.domain.Member; -import shook.shook.member.domain.Nickname; @RequiredArgsConstructor @Service @@ -18,6 +18,7 @@ public class AuthService { private final MemberService memberService; private final GoogleInfoProvider googleInfoProvider; private final TokenProvider tokenProvider; + private final InMemoryTokenPairRepository inMemoryTokenPairRepository; public TokenPair login(final String authorizationCode) { final GoogleAccessTokenResponse accessTokenResponse = @@ -34,16 +35,18 @@ public TokenPair login(final String authorizationCode) { final String nickname = member.getNickname(); final String accessToken = tokenProvider.createAccessToken(memberId, nickname); final String refreshToken = tokenProvider.createRefreshToken(memberId, nickname); + inMemoryTokenPairRepository.addOrUpdateTokenPair(refreshToken, accessToken); return new TokenPair(accessToken, refreshToken); } - public ReissueAccessTokenResponse reissueAccessTokenByRefreshToken(final String refreshToken) { + public ReissueAccessTokenResponse reissueAccessTokenByRefreshToken(final String refreshToken, final String accessToken) { final Claims claims = tokenProvider.parseClaims(refreshToken); final Long memberId = claims.get("memberId", Long.class); final String nickname = claims.get("nickname", String.class); - memberService.findByIdAndNicknameThrowIfNotExist(memberId, new Nickname(nickname)); - final String accessToken = tokenProvider.createAccessToken(memberId, nickname); - return new ReissueAccessTokenResponse(accessToken); + inMemoryTokenPairRepository.validateTokenPair(refreshToken, accessToken); + final String reissuedAccessToken = tokenProvider.createAccessToken(memberId, nickname); + inMemoryTokenPairRepository.addOrUpdateTokenPair(refreshToken, reissuedAccessToken); + return new ReissueAccessTokenResponse(reissuedAccessToken); } } diff --git a/backend/src/main/java/shook/shook/auth/application/TokenPairScheduler.java b/backend/src/main/java/shook/shook/auth/application/TokenPairScheduler.java new file mode 100644 index 000000000..fb01ca738 --- /dev/null +++ b/backend/src/main/java/shook/shook/auth/application/TokenPairScheduler.java @@ -0,0 +1,28 @@ +package shook.shook.auth.application; + +import java.util.Set; +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import shook.shook.auth.exception.TokenException; +import shook.shook.auth.repository.InMemoryTokenPairRepository; + +@RequiredArgsConstructor +@Component +public class TokenPairScheduler { + + private final TokenProvider tokenProvider; + private final InMemoryTokenPairRepository inMemoryTokenPairRepository; + + @Scheduled(cron = "${schedules.cron}") + public void removeExpiredTokenPair() { + final Set refreshTokens = inMemoryTokenPairRepository.getTokenPairs().keySet(); + for (String refreshToken : refreshTokens) { + try { + tokenProvider.parseClaims(refreshToken); + } catch (TokenException.ExpiredTokenException e) { + inMemoryTokenPairRepository.delete(refreshToken); + } + } + } +} diff --git a/backend/src/main/java/shook/shook/auth/application/dto/ReissueAccessTokenResponse.java b/backend/src/main/java/shook/shook/auth/application/dto/ReissueAccessTokenResponse.java index 190bf91e7..5491355b6 100644 --- a/backend/src/main/java/shook/shook/auth/application/dto/ReissueAccessTokenResponse.java +++ b/backend/src/main/java/shook/shook/auth/application/dto/ReissueAccessTokenResponse.java @@ -4,11 +4,11 @@ import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.RequiredArgsConstructor; +import lombok.NoArgsConstructor; @Schema(description = "액세스 토큰 재발급 응답") @AllArgsConstructor -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@NoArgsConstructor(access = AccessLevel.PRIVATE) @Getter public class ReissueAccessTokenResponse { diff --git a/backend/src/main/java/shook/shook/auth/exception/TokenException.java b/backend/src/main/java/shook/shook/auth/exception/TokenException.java index f9a902bbd..dd878627d 100644 --- a/backend/src/main/java/shook/shook/auth/exception/TokenException.java +++ b/backend/src/main/java/shook/shook/auth/exception/TokenException.java @@ -38,4 +38,26 @@ public ExpiredTokenException(final Map inputValuesByProperty) { super(ErrorCode.EXPIRED_TOKEN, inputValuesByProperty); } } + + public static class RefreshTokenNotFoundException extends TokenException { + + public RefreshTokenNotFoundException() { + super(ErrorCode.INVALID_REFRESH_TOKEN); + } + + public RefreshTokenNotFoundException(final Map inputValuesByProperty) { + super(ErrorCode.INVALID_REFRESH_TOKEN, inputValuesByProperty); + } + } + + public static class TokenPairNotMatchingException extends TokenException { + + public TokenPairNotMatchingException() { + super(ErrorCode.TOKEN_PAIR_NOT_MATCHING_EXCEPTION); + } + + public TokenPairNotMatchingException(final Map inputValuesByProperty) { + super(ErrorCode.TOKEN_PAIR_NOT_MATCHING_EXCEPTION, inputValuesByProperty); + } + } } diff --git a/backend/src/main/java/shook/shook/auth/repository/InMemoryTokenPairRepository.java b/backend/src/main/java/shook/shook/auth/repository/InMemoryTokenPairRepository.java new file mode 100644 index 000000000..0920e4b64 --- /dev/null +++ b/backend/src/main/java/shook/shook/auth/repository/InMemoryTokenPairRepository.java @@ -0,0 +1,38 @@ +package shook.shook.auth.repository; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.springframework.stereotype.Repository; +import shook.shook.auth.exception.TokenException; +import shook.shook.auth.exception.TokenException.TokenPairNotMatchingException; + +@Repository +public class InMemoryTokenPairRepository { + + private final Map tokenPairs = new ConcurrentHashMap<>(); + + public void validateTokenPair(final String refreshToken, final String accessToken) { + if (!tokenPairs.containsKey(refreshToken)) { + throw new TokenException.RefreshTokenNotFoundException(Map.of("wrongRefreshToken", refreshToken)); + } + if (!tokenPairs.get(refreshToken).equals(accessToken)) { + throw new TokenPairNotMatchingException(Map.of("wrongAccessToken", accessToken)); + } + } + + public void addOrUpdateTokenPair(final String refreshToken, final String accessToken) { + tokenPairs.put(refreshToken, accessToken); + } + + public void delete(final String refreshToken) { + tokenPairs.remove(refreshToken); + } + + public Map getTokenPairs() { + return tokenPairs; + } + + public void clear() { + tokenPairs.clear(); + } +} diff --git a/backend/src/main/java/shook/shook/auth/ui/AccessTokenReissueController.java b/backend/src/main/java/shook/shook/auth/ui/AccessTokenReissueController.java index 2e45b4f40..16eacf444 100644 --- a/backend/src/main/java/shook/shook/auth/ui/AccessTokenReissueController.java +++ b/backend/src/main/java/shook/shook/auth/ui/AccessTokenReissueController.java @@ -1,9 +1,11 @@ package shook.shook.auth.ui; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CookieValue; -import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RestController; import shook.shook.auth.application.AuthService; import shook.shook.auth.application.dto.ReissueAccessTokenResponse; @@ -16,18 +18,21 @@ public class AccessTokenReissueController implements AccessTokenReissueApi { private static final String EMPTY_REFRESH_TOKEN = "none"; private static final String REFRESH_TOKEN_KEY = "refreshToken"; + private static final String TOKEN_PREFIX = "Bearer "; private final AuthService authService; - @GetMapping("/reissue") + @PostMapping("/reissue") public ResponseEntity reissueAccessToken( - @CookieValue(value = REFRESH_TOKEN_KEY, defaultValue = EMPTY_REFRESH_TOKEN) final String refreshToken + @CookieValue(value = REFRESH_TOKEN_KEY, defaultValue = EMPTY_REFRESH_TOKEN) final String refreshToken, + @RequestHeader(HttpHeaders.AUTHORIZATION) final String authorization ) { if (refreshToken.equals(EMPTY_REFRESH_TOKEN)) { throw new AuthorizationException.RefreshTokenNotFoundException(); } + final String accessToken = authorization.split(TOKEN_PREFIX)[1]; final ReissueAccessTokenResponse response = - authService.reissueAccessTokenByRefreshToken(refreshToken); + authService.reissueAccessTokenByRefreshToken(refreshToken, accessToken); return ResponseEntity.ok(response); } diff --git a/backend/src/main/java/shook/shook/auth/ui/openapi/AccessTokenReissueApi.java b/backend/src/main/java/shook/shook/auth/ui/openapi/AccessTokenReissueApi.java index 17751f063..eff76a302 100644 --- a/backend/src/main/java/shook/shook/auth/ui/openapi/AccessTokenReissueApi.java +++ b/backend/src/main/java/shook/shook/auth/ui/openapi/AccessTokenReissueApi.java @@ -2,10 +2,12 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestHeader; import shook.shook.auth.application.dto.ReissueAccessTokenResponse; @Tag(name = "AccessTokenReissue", description = "액세스 토큰 재발급 API") @@ -19,13 +21,23 @@ public interface AccessTokenReissueApi { responseCode = "200", description = "액세스 토큰 재발급 성공" ) - @Parameter( - name = "refreshToken", - description = "리프레시 토큰", - required = true + @Parameters( + value = { + @Parameter( + name = "refreshToken", + description = "리프레시 토큰", + required = true + ), + @Parameter( + name = "authorization", + description = "authorization 헤더", + required = true + ) + } ) - @GetMapping("/reissue") + @PostMapping("/reissue") ResponseEntity reissueAccessToken( - final String refreshToken + final String refreshToken, + @RequestHeader("Authorization") final String authorization ); } diff --git a/backend/src/main/java/shook/shook/globalexception/ErrorCode.java b/backend/src/main/java/shook/shook/globalexception/ErrorCode.java index 3b29b2fa8..638a75a1b 100644 --- a/backend/src/main/java/shook/shook/globalexception/ErrorCode.java +++ b/backend/src/main/java/shook/shook/globalexception/ErrorCode.java @@ -18,6 +18,8 @@ public enum ErrorCode { REFRESH_TOKEN_NOT_FOUND_EXCEPTION(1006, "accessToken 을 재발급하기 위해서는 refreshToken 이 필요합니다."), ACCESS_TOKEN_NOT_FOUND(1007, "accessToken이 필요합니다."), UNAUTHENTICATED_EXCEPTION(1008, "권한이 없는 요청입니다."), + INVALID_REFRESH_TOKEN(1009, "존재하지 않는 refreshToken 입니다."), + TOKEN_PAIR_NOT_MATCHING_EXCEPTION(1010, "올바르지 않은 TokenPair 입니다."), // 2000: 킬링파트 - 좋아요, 댓글 diff --git a/backend/src/main/resources/application-test.yml b/backend/src/main/resources/application-test.yml index 9f6b6246e..d27546647 100644 --- a/backend/src/main/resources/application-test.yml +++ b/backend/src/main/resources/application-test.yml @@ -42,5 +42,6 @@ excel: song-length-suffix: "s" schedules: + cron: 0/1 * * * * * in-memory-song: cron: "0/1 * * * * *" # 1초 diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 60d063b31..7f77e528b 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -59,6 +59,9 @@ excel: killingpart-data-delimiter: " " song-length-suffix: "초" +# Properties Only For local +schedules: + cron: "0 0 0/1 * * *" schedules: in-memory-song: cron: "0 0 0/1 * * *" #1시간 diff --git a/backend/src/test/java/shook/shook/auth/application/AuthServiceTest.java b/backend/src/test/java/shook/shook/auth/application/AuthServiceTest.java index 4c112efec..ecc31b79a 100644 --- a/backend/src/test/java/shook/shook/auth/application/AuthServiceTest.java +++ b/backend/src/test/java/shook/shook/auth/application/AuthServiceTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; @@ -17,6 +18,7 @@ import shook.shook.auth.application.dto.ReissueAccessTokenResponse; import shook.shook.auth.application.dto.TokenPair; import shook.shook.auth.exception.TokenException; +import shook.shook.auth.repository.InMemoryTokenPairRepository; import shook.shook.member.application.MemberService; import shook.shook.member.domain.Member; import shook.shook.member.domain.repository.MemberRepository; @@ -35,8 +37,15 @@ class AuthServiceTest { @Autowired private MemberService memberService; + @Autowired + private InMemoryTokenPairRepository inMemoryTokenPairRepository; + private TokenProvider tokenProvider; + private String refreshToken; + + private String accessToken; + private AuthService authService; @BeforeEach @@ -45,8 +54,11 @@ void setUp() { 100000L, 1000000L, "asdfsdsvsdf2esvsdvsdvs23"); - authService = new AuthService(memberService, googleInfoProvider, tokenProvider); savedMember = memberRepository.save(new Member("shook@wooteco.com", "shook")); + refreshToken = tokenProvider.createRefreshToken(savedMember.getId(), savedMember.getNickname()); + accessToken = tokenProvider.createAccessToken(savedMember.getId(), savedMember.getNickname()); + inMemoryTokenPairRepository.addOrUpdateTokenPair(refreshToken, accessToken); + authService = new AuthService(memberService, googleInfoProvider, tokenProvider, inMemoryTokenPairRepository); } @AfterEach @@ -80,19 +92,16 @@ void success_login() { assertThat(result.getAccessToken()).isEqualTo(accessToken); assertThat(result.getRefreshToken()).isEqualTo(refreshToken); + assertDoesNotThrow(() -> inMemoryTokenPairRepository.validateTokenPair(refreshToken, accessToken)); } - @DisplayName("올바른 refresh 토큰이 들어오면 access 토큰을 재발급해준다.") + @DisplayName("올바른 refresh 토큰과 access 토큰이 들어오면 access 토큰을 재발급해준다.") @Test void success_reissue() { //given - final String refreshToken = tokenProvider.createRefreshToken( - savedMember.getId(), - savedMember.getNickname()); - //when final ReissueAccessTokenResponse result = authService.reissueAccessTokenByRefreshToken( - refreshToken); + refreshToken, accessToken); //then final String accessToken = tokenProvider.createAccessToken( @@ -111,13 +120,13 @@ void fail_reissue_invalid_refreshToken() { 100L, "asdzzxcwetg2adfvssd3xZcZXCZvzx"); - final String refreshToken = inValidTokenProvider.createRefreshToken( + final String wrongRefreshToken = inValidTokenProvider.createRefreshToken( savedMember.getId(), savedMember.getNickname()); //when //then - assertThatThrownBy(() -> authService.reissueAccessTokenByRefreshToken(refreshToken)) + assertThatThrownBy(() -> authService.reissueAccessTokenByRefreshToken(wrongRefreshToken, accessToken)) .isInstanceOf(TokenException.NotIssuedTokenException.class); } @@ -136,7 +145,7 @@ void fail_reissue_expired_refreshToken() { //when //then - assertThatThrownBy(() -> authService.reissueAccessTokenByRefreshToken(refreshToken)) + assertThatThrownBy(() -> authService.reissueAccessTokenByRefreshToken(refreshToken, accessToken)) .isInstanceOf(TokenException.ExpiredTokenException.class); } } diff --git a/backend/src/test/java/shook/shook/auth/application/TokenPairSchedulerTest.java b/backend/src/test/java/shook/shook/auth/application/TokenPairSchedulerTest.java new file mode 100644 index 000000000..05aa20c2c --- /dev/null +++ b/backend/src/test/java/shook/shook/auth/application/TokenPairSchedulerTest.java @@ -0,0 +1,85 @@ +package shook.shook.auth.application; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; +import java.util.concurrent.TimeUnit; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.scheduling.annotation.EnableScheduling; +import shook.shook.auth.repository.InMemoryTokenPairRepository; + +@EnableScheduling +@SpringBootTest +class TokenPairSchedulerTest { + + @Autowired + private InMemoryTokenPairRepository inMemoryTokenPairRepository; + + private TokenProvider tokenProvider; + private TokenProvider expiredTokenProvider; + + @BeforeEach + void setting() { + tokenProvider = new TokenProvider(1200, 634000, "asdkfwofk23ksdfowsrk4sdkf"); + expiredTokenProvider = new TokenProvider(0, 0, "asdkfwofk23ksdfowsrk4sdkf"); + } + + @AfterEach + void clear() { + inMemoryTokenPairRepository.clear(); + } + + @DisplayName("tokenPairSechduler를 통해 inMemoryTokenPairRepository를 갱신한다.") + @Test + void renewInMemoryTokenPairRepository() { + // given + final String refreshToken = tokenProvider.createRefreshToken(1L, "shook"); + final String accessToken = tokenProvider.createAccessToken(1L, "shook"); + final String expiredRefreshToken = expiredTokenProvider.createRefreshToken(2L, "expiredShook"); + final String expiredAccessToken = expiredTokenProvider.createAccessToken(2L, "expiredShook"); + + inMemoryTokenPairRepository.addOrUpdateTokenPair(refreshToken, accessToken); + inMemoryTokenPairRepository.addOrUpdateTokenPair(expiredRefreshToken, expiredAccessToken); + + // when + final TokenPairScheduler tokenPairScheduler = new TokenPairScheduler(tokenProvider, inMemoryTokenPairRepository); + tokenPairScheduler.removeExpiredTokenPair(); + + // then + final Map tokenPairs = inMemoryTokenPairRepository.getTokenPairs(); + + assertThat(tokenPairs.size()).isOne(); + assertThat(tokenPairs.get(refreshToken)).isEqualTo(accessToken); + assertThat(tokenPairs.containsKey(expiredRefreshToken)).isFalse(); + } + + @Test + @DisplayName("1초마다 동작하는 scheduler로 inMemoryTokenPairRepository를 갱신한다.") + void renewInMemoryTokenPairRepositoryWithScheduler() { + // given + final String refreshToken = tokenProvider.createRefreshToken(1L, "shook"); + final String accessToken = tokenProvider.createAccessToken(1L, "shook"); + final String expiredRefreshToken = expiredTokenProvider.createRefreshToken(2L, "expiredShook"); + final String expiredAccessToken = expiredTokenProvider.createAccessToken(2L, "expiredShook"); + + inMemoryTokenPairRepository.addOrUpdateTokenPair(refreshToken, accessToken); + inMemoryTokenPairRepository.addOrUpdateTokenPair(expiredRefreshToken, expiredAccessToken); + + // when + final Map tokenPairs = inMemoryTokenPairRepository.getTokenPairs(); + // then + Awaitility.await() + .atMost(2, TimeUnit.SECONDS) + .untilAsserted(() -> { + assertThat(tokenPairs.size()).isOne(); + assertThat(tokenPairs.get(refreshToken)).isEqualTo(accessToken); + assertThat(tokenPairs.containsKey(expiredRefreshToken)).isFalse(); + }); + } +} diff --git a/backend/src/test/java/shook/shook/auth/repository/InMemoryTokenPairRepositoryTest.java b/backend/src/test/java/shook/shook/auth/repository/InMemoryTokenPairRepositoryTest.java new file mode 100644 index 000000000..12e864735 --- /dev/null +++ b/backend/src/test/java/shook/shook/auth/repository/InMemoryTokenPairRepositoryTest.java @@ -0,0 +1,50 @@ +package shook.shook.auth.repository; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import shook.shook.auth.exception.TokenException.RefreshTokenNotFoundException; +import shook.shook.auth.exception.TokenException.TokenPairNotMatchingException; + +class InMemoryTokenPairRepositoryTest { + + @DisplayName("refreshToken과 accessToken이 올바른 쌍을 가지고 있으면 예외를 처리하지 않는다.") + @Test + void successValidateTokenPair() { + // given + final InMemoryTokenPairRepository inMemoryTokenPairRepository = new InMemoryTokenPairRepository(); + inMemoryTokenPairRepository.addOrUpdateTokenPair("refreshToken", "accessToken"); + + // when + // then + assertDoesNotThrow(() -> inMemoryTokenPairRepository.validateTokenPair("refreshToken", "accessToken")); + } + + @DisplayName("존재하지 않는 refreshToken이면 예외를 발생한다.") + @Test + void failValidateTokenPair_refreshTokenNotExist() { + // given + final InMemoryTokenPairRepository inMemoryTokenPairRepository = new InMemoryTokenPairRepository(); + inMemoryTokenPairRepository.addOrUpdateTokenPair("refreshToken", "accessToken"); + + // when + // then + assertThatThrownBy(() -> inMemoryTokenPairRepository.validateTokenPair("wrongRefreshToken", "accessToken")) + .isInstanceOf(RefreshTokenNotFoundException.class); + } + + @DisplayName("refreshToken에 해당하는 accessToken이 매칭되지 않으면 예외를 발생한다.") + @Test + void failValidateTokenPair_notMatching() { + // given + final InMemoryTokenPairRepository inMemoryTokenPairRepository = new InMemoryTokenPairRepository(); + inMemoryTokenPairRepository.addOrUpdateTokenPair("refreshToken", "accessToken"); + + // when + // then + assertThatThrownBy(() -> inMemoryTokenPairRepository.validateTokenPair("refreshToken", "wrongAccessToken")) + .isInstanceOf(TokenPairNotMatchingException.class); + } +} diff --git a/backend/src/test/java/shook/shook/auth/ui/AccessTokenReissueControllerTest.java b/backend/src/test/java/shook/shook/auth/ui/AccessTokenReissueControllerTest.java index 0d3f3aaa1..36677e163 100644 --- a/backend/src/test/java/shook/shook/auth/ui/AccessTokenReissueControllerTest.java +++ b/backend/src/test/java/shook/shook/auth/ui/AccessTokenReissueControllerTest.java @@ -15,6 +15,7 @@ import org.springframework.http.HttpStatus; import shook.shook.auth.application.TokenProvider; import shook.shook.auth.application.dto.ReissueAccessTokenResponse; +import shook.shook.auth.repository.InMemoryTokenPairRepository; import shook.shook.member.domain.Member; import shook.shook.member.domain.repository.MemberRepository; import shook.shook.support.DataCleaner; @@ -36,11 +37,20 @@ class AccessTokenReissueControllerTest { @Autowired private TokenProvider tokenProvider; + @Autowired + private InMemoryTokenPairRepository inMemoryTokenPairRepository; + + private String refreshToken; + private String accessToken; + @BeforeEach void setUp() { RestAssured.port = port; dataCleaner.clear(); savedMember = memberRepository.save(new Member("shook@wooteco.com", "shook")); + refreshToken = tokenProvider.createRefreshToken(savedMember.getId(), savedMember.getNickname()); + accessToken = tokenProvider.createAccessToken(savedMember.getId(), savedMember.getNickname()); + inMemoryTokenPairRepository.addOrUpdateTokenPair(refreshToken, accessToken); } @AfterEach @@ -52,14 +62,13 @@ void delete() { @Test void success_reissue_accessToken() { //given - final String refreshToken = tokenProvider.createRefreshToken( - savedMember.getId(), - savedMember.getNickname()); + final String authorization = "Bearer " + accessToken; //when final ReissueAccessTokenResponse response = RestAssured.given().log().all() + .header("Authorization", authorization) .cookie("refreshToken", refreshToken) - .when().log().all().get("/reissue") + .when().log().all().post("/reissue") .then().statusCode(HttpStatus.OK.value()) .extract().body().as(ReissueAccessTokenResponse.class); @@ -76,7 +85,8 @@ void fail_reissue_accessToken() { //when //then RestAssured.given().log().all() - .when().log().all().get("/reissue") + .header("Authorization", "authorization") + .when().log().all().post("/reissue") .then().statusCode(HttpStatus.UNAUTHORIZED.value()); } } diff --git a/backend/src/test/java/shook/shook/exceptionhandler/ControllerAdviceTest.java b/backend/src/test/java/shook/shook/exceptionhandler/ControllerAdviceTest.java index cec234ba9..9960644a1 100644 --- a/backend/src/test/java/shook/shook/exceptionhandler/ControllerAdviceTest.java +++ b/backend/src/test/java/shook/shook/exceptionhandler/ControllerAdviceTest.java @@ -47,6 +47,8 @@ private static Stream exceptionTestData() { return Stream.of( new ExceptionTestData(new TokenException.NotIssuedTokenException(), 401), new ExceptionTestData(new TokenException.ExpiredTokenException(), 401), + new ExceptionTestData(new TokenException.TokenPairNotMatchingException(), 401), + new ExceptionTestData(new TokenException.RefreshTokenNotFoundException(), 401), new ExceptionTestData(new OAuthException.InvalidAuthorizationCodeException(), 503), new ExceptionTestData(new OAuthException.InvalidAccessTokenException(), 503),