From 439ebc7892a03fc04249fc2e464a07322173e230 Mon Sep 17 00:00:00 2001 From: kdkdhoho Date: Sun, 1 Sep 2024 12:30:27 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=20feat&refactor:=20Cookie=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81=20?= =?UTF-8?q?(#282)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/application/domain/JwtManager.java | 4 +- .../domain/kakao/KakaoOauthClient.java | 11 +- .../dto/LoginResponse.java | 22 +-- .../auth/application/dto/LoginResult.java | 50 ------ .../dto/UpdateTokenResponse.java | 2 +- .../application/dto/UpdateTokenResult.java | 13 -- .../auth/application/service/AuthService.java | 47 ++---- .../auth/presentation/AuthController.java | 75 +-------- .../common/auth/AuthorizationInterceptor.java | 33 ++-- .../listywave/common/auth/TokenReader.java | 36 ----- .../acceptance/auth/AuthAcceptanceTest.java | 149 +++++++----------- .../auth/AuthAcceptanceTestHelper.java | 18 +-- .../acceptance/common/AcceptanceTest.java | 18 +-- .../application/domain/JwtManagerTest.java | 47 +++--- .../auth/AuthorizationInterceptorTest.java | 37 +---- .../common/auth/TokenReaderTest.java | 68 -------- 16 files changed, 151 insertions(+), 479 deletions(-) rename src/main/java/com/listywave/auth/{presentation => application}/dto/LoginResponse.java (55%) delete mode 100644 src/main/java/com/listywave/auth/application/dto/LoginResult.java rename src/main/java/com/listywave/auth/{presentation => application}/dto/UpdateTokenResponse.java (60%) delete mode 100644 src/main/java/com/listywave/auth/application/dto/UpdateTokenResult.java delete mode 100644 src/main/java/com/listywave/common/auth/TokenReader.java delete mode 100644 src/test/java/com/listywave/common/auth/TokenReaderTest.java diff --git a/src/main/java/com/listywave/auth/application/domain/JwtManager.java b/src/main/java/com/listywave/auth/application/domain/JwtManager.java index c0c1ee00..647d433b 100644 --- a/src/main/java/com/listywave/auth/application/domain/JwtManager.java +++ b/src/main/java/com/listywave/auth/application/domain/JwtManager.java @@ -48,7 +48,7 @@ public JwtManager( public String createAccessToken(Long userId) { Date now = new Date(); return Jwts.builder() - .header().type("jwt").and() + .header().type("accessToken").and() .signWith(secretKey) .issuer(issuer) .issuedAt(now) @@ -61,7 +61,7 @@ public String createAccessToken(Long userId) { public String createRefreshToken(Long userId) { Date now = new Date(); return Jwts.builder() - .header().type("jwt").and() + .header().type("refreshToken").and() .signWith(secretKey) .issuer(issuer) .issuedAt(now) diff --git a/src/main/java/com/listywave/auth/application/domain/kakao/KakaoOauthClient.java b/src/main/java/com/listywave/auth/application/domain/kakao/KakaoOauthClient.java index 3bc9effd..0ee99caf 100644 --- a/src/main/java/com/listywave/auth/application/domain/kakao/KakaoOauthClient.java +++ b/src/main/java/com/listywave/auth/application/domain/kakao/KakaoOauthClient.java @@ -1,7 +1,6 @@ package com.listywave.auth.application.domain.kakao; import com.listywave.auth.infra.kakao.KakaoOauthApiClient; -import com.listywave.auth.infra.kakao.response.KakaoLogoutResponse; import com.listywave.auth.infra.kakao.response.KakaoMember; import com.listywave.auth.infra.kakao.response.KakaoTokenResponse; import lombok.RequiredArgsConstructor; @@ -13,6 +12,7 @@ @RequiredArgsConstructor public class KakaoOauthClient { + private static final String TOKEN_PREFIX = "Bearer "; private final KakaoOauthConfig kakaoOauthConfig; private final KakaoOauthApiClient apiClient; @@ -29,12 +29,11 @@ public KakaoTokenResponse requestToken(String authCode) { } public KakaoMember fetchMember(String accessToken) { - return apiClient.fetchKakaoMember("Bearer " + accessToken); + return apiClient.fetchKakaoMember(TOKEN_PREFIX + accessToken); } - public Long logout(String oauthAccessToken) { - String accessToken = "Bearer " + oauthAccessToken; - KakaoLogoutResponse response = apiClient.logout(accessToken); - return response.id(); + public void logout(String oauthAccessToken) { + String accessToken = TOKEN_PREFIX + oauthAccessToken; + apiClient.logout(accessToken); } } diff --git a/src/main/java/com/listywave/auth/presentation/dto/LoginResponse.java b/src/main/java/com/listywave/auth/application/dto/LoginResponse.java similarity index 55% rename from src/main/java/com/listywave/auth/presentation/dto/LoginResponse.java rename to src/main/java/com/listywave/auth/application/dto/LoginResponse.java index bf0af3a1..6921cfa1 100644 --- a/src/main/java/com/listywave/auth/presentation/dto/LoginResponse.java +++ b/src/main/java/com/listywave/auth/application/dto/LoginResponse.java @@ -1,6 +1,5 @@ -package com.listywave.auth.presentation.dto; +package com.listywave.auth.application.dto; -import com.listywave.auth.application.dto.LoginResult; import com.listywave.user.application.domain.User; import lombok.Builder; @@ -18,22 +17,7 @@ public record LoginResponse( String refreshToken ) { - public static LoginResponse of(LoginResult result) { - return LoginResponse.builder() - .id(result.id()) - .profileImageUrl(result.profileImageUrl()) - .backgroundImageUrl(result.backgroundImageUrl()) - .nickname(result.nickname()) - .description(result.description()) - .followerCount(result.followerCount()) - .followingCount(result.followingCount()) - .isFirst(result.isFirst()) - .accessToken(result.accessToken()) - .refreshToken(result.refreshToken()) - .build(); - } - - public static LoginResponse of(User user, String accessToken, String refreshToken) { + public static LoginResponse of(User user, String accessToken, String refreshToken, boolean isFirst) { return LoginResponse.builder() .id(user.getId()) .profileImageUrl(user.getProfileImageUrl()) @@ -42,7 +26,7 @@ public static LoginResponse of(User user, String accessToken, String refreshToke .description(user.getDescription()) .followerCount(user.getFollowerCount()) .followingCount(user.getFollowingCount()) - .isFirst(false) + .isFirst(isFirst) .accessToken(accessToken) .refreshToken(refreshToken) .build(); diff --git a/src/main/java/com/listywave/auth/application/dto/LoginResult.java b/src/main/java/com/listywave/auth/application/dto/LoginResult.java deleted file mode 100644 index cee76d32..00000000 --- a/src/main/java/com/listywave/auth/application/dto/LoginResult.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.listywave.auth.application.dto; - -import com.listywave.user.application.domain.User; -import java.util.concurrent.TimeUnit; - -public record LoginResult( - Long id, - String profileImageUrl, - String backgroundImageUrl, - String nickname, - String description, - int followingCount, - int followerCount, - boolean isFirst, - String accessToken, - String refreshToken, - int accessTokenValidTimeDuration, - int refreshTokenValidTimeDuration, - TimeUnit accessTokenValidTimeUnit, - TimeUnit refreshTokenValidTimeUnit -) { - - public static LoginResult of( - User user, - boolean isFirst, - String accessToken, - String refreshToken, - int accessTokenValidTimeDuration, - int refreshTokenValidTimeDuration, - TimeUnit accessTokenValidTimeUnit, - TimeUnit refreshTokenValidTimeUnit - ) { - return new LoginResult( - user.getId(), - user.getProfileImageUrl(), - user.getBackgroundImageUrl(), - user.getNickname(), - user.getDescription(), - user.getFollowingCount(), - user.getFollowerCount(), - isFirst, - accessToken, - refreshToken, - accessTokenValidTimeDuration, - refreshTokenValidTimeDuration, - accessTokenValidTimeUnit, - refreshTokenValidTimeUnit - ); - } -} diff --git a/src/main/java/com/listywave/auth/presentation/dto/UpdateTokenResponse.java b/src/main/java/com/listywave/auth/application/dto/UpdateTokenResponse.java similarity index 60% rename from src/main/java/com/listywave/auth/presentation/dto/UpdateTokenResponse.java rename to src/main/java/com/listywave/auth/application/dto/UpdateTokenResponse.java index 1b32e127..bb16a28a 100644 --- a/src/main/java/com/listywave/auth/presentation/dto/UpdateTokenResponse.java +++ b/src/main/java/com/listywave/auth/application/dto/UpdateTokenResponse.java @@ -1,4 +1,4 @@ -package com.listywave.auth.presentation.dto; +package com.listywave.auth.application.dto; public record UpdateTokenResponse( String accessToken diff --git a/src/main/java/com/listywave/auth/application/dto/UpdateTokenResult.java b/src/main/java/com/listywave/auth/application/dto/UpdateTokenResult.java deleted file mode 100644 index 105a5638..00000000 --- a/src/main/java/com/listywave/auth/application/dto/UpdateTokenResult.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.listywave.auth.application.dto; - -import java.util.concurrent.TimeUnit; - -public record UpdateTokenResult( - String accessToken, - String refreshToken, - int accessTokenValidTimeDuration, - int refreshTokenValidTimeDuration, - TimeUnit accessTokenValidTimeUnit, - TimeUnit refreshTokenValidTimeUnit -) { -} diff --git a/src/main/java/com/listywave/auth/application/service/AuthService.java b/src/main/java/com/listywave/auth/application/service/AuthService.java index 8d753996..dac45be1 100644 --- a/src/main/java/com/listywave/auth/application/service/AuthService.java +++ b/src/main/java/com/listywave/auth/application/service/AuthService.java @@ -5,8 +5,8 @@ import com.listywave.auth.application.domain.JwtManager; import com.listywave.auth.application.domain.kakao.KakaoOauthClient; import com.listywave.auth.application.domain.kakao.KakaoRedirectUriProvider; -import com.listywave.auth.application.dto.LoginResult; -import com.listywave.auth.application.dto.UpdateTokenResult; +import com.listywave.auth.application.dto.LoginResponse; +import com.listywave.auth.application.dto.UpdateTokenResponse; import com.listywave.auth.infra.kakao.response.KakaoMember; import com.listywave.auth.infra.kakao.response.KakaoTokenResponse; import com.listywave.common.exception.CustomException; @@ -38,7 +38,7 @@ public String provideRedirectUri() { return kakaoRedirectUriProvider.provide(); } - public LoginResult login(String authCode) { + public LoginResponse login(String authCode) { KakaoTokenResponse kakaoTokenResponse = kakaoOauthClient.requestToken(authCode); KakaoMember kakaoMember = kakaoOauthClient.fetchMember(kakaoTokenResponse.accessToken()); @@ -51,40 +51,22 @@ public LoginResult login(String authCode) { ); } - private LoginResult loginNonInit(User user, String kakaoAccessToken) { + private LoginResponse loginNonInit(User user, String kakaoAccessToken) { if (user.isDelete()) { throw new CustomException(DELETED_USER_EXCEPTION); } user.updateKakaoAccessToken(kakaoAccessToken); String accessToken = jwtManager.createAccessToken(user.getId()); String refreshToken = jwtManager.createRefreshToken(user.getId()); - return LoginResult.of( - user, - false, - accessToken, - refreshToken, - jwtManager.getAccessTokenValidTimeDuration(), - jwtManager.getRefreshTokenValidTimeDuration(), - jwtManager.getAccessTokenValidTimeUnit(), - jwtManager.getRefreshTokenValidTimeUnit() - ); + return LoginResponse.of(user, accessToken, refreshToken, false); } - private LoginResult loginInit(Long kakaoId, String kakaoEmail, String kakaoAccessToken) { + private LoginResponse loginInit(Long kakaoId, String kakaoEmail, String kakaoAccessToken) { User user = User.init(kakaoId, kakaoEmail, kakaoAccessToken); - User createdUser = userRepository.save(user); + userRepository.save(user); String accessToken = jwtManager.createAccessToken(user.getId()); String refreshToken = jwtManager.createRefreshToken(user.getId()); - return LoginResult.of( - createdUser, - true, - accessToken, - refreshToken, - jwtManager.getAccessTokenValidTimeDuration(), - jwtManager.getRefreshTokenValidTimeDuration(), - jwtManager.getAccessTokenValidTimeUnit(), - jwtManager.getRefreshTokenValidTimeUnit() - ); + return LoginResponse.of(user, accessToken, refreshToken, true); } public void logout(Long userId) { @@ -95,19 +77,10 @@ public void logout(Long userId) { } @Transactional(readOnly = true) - public UpdateTokenResult updateToken(Long userId) { + public UpdateTokenResponse updateToken(Long userId) { User user = userRepository.getById(userId); - String accessToken = jwtManager.createAccessToken(user.getId()); - String newRefreshToken = jwtManager.createRefreshToken(user.getId()); - return new UpdateTokenResult( - accessToken, - newRefreshToken, - jwtManager.getAccessTokenValidTimeDuration(), - jwtManager.getRefreshTokenValidTimeDuration(), - jwtManager.getAccessTokenValidTimeUnit(), - jwtManager.getRefreshTokenValidTimeUnit() - ); + return new UpdateTokenResponse(accessToken); } public void withdraw(Long userId) { diff --git a/src/main/java/com/listywave/auth/presentation/AuthController.java b/src/main/java/com/listywave/auth/presentation/AuthController.java index 6f301f30..2db1725c 100644 --- a/src/main/java/com/listywave/auth/presentation/AuthController.java +++ b/src/main/java/com/listywave/auth/presentation/AuthController.java @@ -1,20 +1,12 @@ package com.listywave.auth.presentation; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.springframework.http.HttpHeaders.SET_COOKIE; - -import com.listywave.auth.application.dto.LoginResult; -import com.listywave.auth.application.dto.UpdateTokenResult; +import com.listywave.auth.application.dto.LoginResponse; +import com.listywave.auth.application.dto.UpdateTokenResponse; import com.listywave.auth.application.service.AuthService; -import com.listywave.auth.presentation.dto.LoginResponse; -import com.listywave.auth.presentation.dto.UpdateTokenResponse; import com.listywave.common.auth.Auth; -import com.listywave.common.util.TimeUtils; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; -import java.time.Duration; import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -36,45 +28,9 @@ ResponseEntity redirectAuthCodeRequestUrl(HttpServletResponse response) th } @GetMapping("/auth/redirect/kakao") - ResponseEntity login( - @RequestParam("code") String authCode - ) { - LoginResult loginResult = authService.login(authCode); - - ResponseCookie accessTokenCookie = createCookie( - "accessToken", - loginResult.accessToken(), - Duration.ofSeconds( - TimeUtils.convertTimeUnit(loginResult.accessTokenValidTimeDuration(), - loginResult.accessTokenValidTimeUnit(), - SECONDS) - ), true, true, "sameSite" - ); - ResponseCookie refreshTokenCookie = createCookie( - "refreshToken", - loginResult.refreshToken(), - Duration.ofSeconds( - TimeUtils.convertTimeUnit(loginResult.refreshTokenValidTimeDuration(), - loginResult.refreshTokenValidTimeUnit(), - SECONDS) - ), true, true, "sameSite" - ); - LoginResponse response = LoginResponse.of(loginResult); - - return ResponseEntity.ok() - .header(SET_COOKIE, accessTokenCookie.toString()) - .header(SET_COOKIE, refreshTokenCookie.toString()) - .body(response); - } - - private ResponseCookie createCookie(String name, String value, Duration maxAge, boolean httpOnly, boolean secure, String sameSite) { - return ResponseCookie.from(name) - .value(value) - .maxAge(maxAge) - .httpOnly(httpOnly) - .secure(secure) - .sameSite(sameSite) - .build(); + ResponseEntity login(@RequestParam("code") String authCode) { + LoginResponse response = authService.login(authCode); + return ResponseEntity.ok().body(response); } @PatchMapping("/auth/kakao") @@ -84,24 +40,9 @@ ResponseEntity logout(@Auth Long loginUserId) { } @GetMapping("/auth/token") - ResponseEntity updateToken( - @Auth Long userId - ) { - UpdateTokenResult result = authService.updateToken(userId); - - ResponseCookie accessTokenCookie = createCookie( - "accessToken", - result.accessToken(), - Duration.ofSeconds( - TimeUtils.convertTimeUnit(result.accessTokenValidTimeDuration(), - result.accessTokenValidTimeUnit(), - SECONDS) - ), true, true, "sameSite" - ); - - return ResponseEntity.ok() - .header(SET_COOKIE, accessTokenCookie.toString()) - .body(new UpdateTokenResponse(result.accessToken())); + ResponseEntity updateToken(@Auth Long userId) { + UpdateTokenResponse response = authService.updateToken(userId); + return ResponseEntity.ok().body(response); } @DeleteMapping("/withdraw") diff --git a/src/main/java/com/listywave/common/auth/AuthorizationInterceptor.java b/src/main/java/com/listywave/common/auth/AuthorizationInterceptor.java index a4316332..d0ae5120 100644 --- a/src/main/java/com/listywave/common/auth/AuthorizationInterceptor.java +++ b/src/main/java/com/listywave/common/auth/AuthorizationInterceptor.java @@ -1,13 +1,16 @@ package com.listywave.common.auth; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; import static org.springframework.http.HttpMethod.GET; import static org.springframework.http.HttpMethod.OPTIONS; +import com.listywave.auth.application.domain.JwtManager; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.util.Arrays; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpMethod; +import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.method.HandlerMethod; @@ -35,8 +38,8 @@ public class AuthorizationInterceptor implements HandlerInterceptor { new UriAndMethod("/users/basic-background-image", GET), }; + private final JwtManager jwtManager; private final AuthContext authContext; - private final TokenReader tokenReader; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { @@ -46,11 +49,11 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons if (!(handler instanceof HandlerMethod)) { return true; } - if (isNonRequiredAuthentication(handler)) { + if (doesNotRequiredAuthentication(handler)) { return true; } - Long userId = tokenReader.readAccessToken(request); + Long userId = readAccessToken(request); authContext.setUserId(userId); return true; } @@ -59,7 +62,7 @@ private boolean isPreflight(HttpServletRequest request) { return request.getMethod().equals(OPTIONS.name()); } - private boolean isNonRequiredAuthentication(Object handler) { + private boolean doesNotRequiredAuthentication(Object handler) { HandlerMethod handlerMethod = (HandlerMethod) handler; RequestMapping requestMapping = handlerMethod.getMethodAnnotation(RequestMapping.class); String mappingUri = requestMapping.value()[0]; @@ -68,14 +71,22 @@ private boolean isNonRequiredAuthentication(Object handler) { return Arrays.stream(whiteList) .anyMatch(it -> it.isMatch(mappingUri, mappingMethod)); } -} -record UriAndMethod( - String uri, - HttpMethod method -) { + @Nullable + private Long readAccessToken(HttpServletRequest request) { + String authorizationValue = request.getHeader(AUTHORIZATION); + if (authorizationValue == null || authorizationValue.isBlank()) { + return null; + } + return jwtManager.readTokenWithPrefix(authorizationValue); + } - public boolean isMatch(String mappingUrl, HttpMethod method) { - return this.uri.equals(mappingUrl) && this.method.equals(method); + private record UriAndMethod( + String uri, + HttpMethod method + ) { + public boolean isMatch(String mappingUrl, HttpMethod method) { + return this.uri.equals(mappingUrl) && this.method.equals(method); + } } } diff --git a/src/main/java/com/listywave/common/auth/TokenReader.java b/src/main/java/com/listywave/common/auth/TokenReader.java deleted file mode 100644 index 3008f873..00000000 --- a/src/main/java/com/listywave/common/auth/TokenReader.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.listywave.common.auth; - -import static org.springframework.http.HttpHeaders.AUTHORIZATION; - -import com.listywave.auth.application.domain.JwtManager; -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import java.util.Arrays; -import lombok.RequiredArgsConstructor; -import org.springframework.lang.Nullable; -import org.springframework.stereotype.Component; - -@Component -@RequiredArgsConstructor -public class TokenReader { - - private final JwtManager jwtManager; - - @Nullable - public Long readAccessToken(HttpServletRequest request) { - String authorizationValue = request.getHeader(AUTHORIZATION); - if (authorizationValue != null && !authorizationValue.isBlank()) { - return jwtManager.readTokenWithPrefix(authorizationValue); - } - - Cookie[] cookies = request.getCookies(); - if (cookies == null || cookies.length == 0) { - return null; - } - return Arrays.stream(cookies) - .filter(cookie -> cookie.getName().equals("accessToken") || cookie.getName().equals("refreshToken")) - .findFirst() - .map(cookie -> jwtManager.readTokenWithoutPrefix(cookie.getValue())) - .orElse(null); - } -} diff --git a/src/test/java/com/listywave/acceptance/auth/AuthAcceptanceTest.java b/src/test/java/com/listywave/acceptance/auth/AuthAcceptanceTest.java index 9ef22da1..4df62d50 100644 --- a/src/test/java/com/listywave/acceptance/auth/AuthAcceptanceTest.java +++ b/src/test/java/com/listywave/acceptance/auth/AuthAcceptanceTest.java @@ -1,11 +1,10 @@ package com.listywave.acceptance.auth; -import static com.listywave.acceptance.auth.AuthAcceptanceTestHelper.Authorization_헤더에_리프레시_토큰을_담아_액세스_토큰_재발급_요청; -import static com.listywave.acceptance.auth.AuthAcceptanceTestHelper.Cookie에_리프레시_토큰을_담아_액세스_토큰_재발급_요청; -import static com.listywave.acceptance.auth.AuthAcceptanceTestHelper.로그아웃_요청; -import static com.listywave.acceptance.auth.AuthAcceptanceTestHelper.로그인_요청; -import static com.listywave.acceptance.auth.AuthAcceptanceTestHelper.카카오_로그인_페이지_요청; -import static com.listywave.acceptance.auth.AuthAcceptanceTestHelper.회원탈퇴_요청; +import static com.listywave.acceptance.auth.AuthAcceptanceTestHelper.로그아웃_API; +import static com.listywave.acceptance.auth.AuthAcceptanceTestHelper.로그인_API; +import static com.listywave.acceptance.auth.AuthAcceptanceTestHelper.액세스_토큰_재발급_API; +import static com.listywave.acceptance.auth.AuthAcceptanceTestHelper.카카오_로그인_페이지_요청_API; +import static com.listywave.acceptance.auth.AuthAcceptanceTestHelper.회원탈퇴_API; import static com.listywave.acceptance.common.CommonAcceptanceHelper.HTTP_상태_코드를_검증한다; import static com.listywave.acceptance.user.UserAcceptanceTestHelper.비회원_회원_정보_조회_요청; import static com.listywave.common.exception.ErrorCode.DELETED_USER_EXCEPTION; @@ -19,13 +18,12 @@ import static org.springframework.http.HttpStatus.OK; import com.listywave.acceptance.common.AcceptanceTest; -import com.listywave.auth.application.dto.LoginResult; +import com.listywave.auth.application.dto.LoginResponse; +import com.listywave.auth.application.dto.UpdateTokenResponse; import com.listywave.auth.infra.kakao.response.KakaoLogoutResponse; import com.listywave.auth.infra.kakao.response.KakaoMember; import com.listywave.auth.infra.kakao.response.KakaoMember.KakaoAccount; import com.listywave.auth.infra.kakao.response.KakaoTokenResponse; -import com.listywave.auth.presentation.dto.LoginResponse; -import com.listywave.auth.presentation.dto.UpdateTokenResponse; import com.listywave.common.exception.ErrorResponse; import com.listywave.image.application.domain.DefaultBackgroundImages; import com.listywave.image.application.domain.DefaultProfileImages; @@ -37,33 +35,31 @@ @DisplayName("인증 관련 인수테스트") public class AuthAcceptanceTest extends AcceptanceTest { - private static final KakaoTokenResponse expectedKakaoTokenResponse = new KakaoTokenResponse("Bearer", "accessToken", Integer.MAX_VALUE, "refreshToken", Integer.MAX_VALUE, "email"); - private static final KakaoMember expectedKakaoMember = new KakaoMember(1L, new KakaoAccount(true, true, true, "email")); + private static final KakaoTokenResponse EXPECTED_KAKAO_TOKEN_RESPONSE = new KakaoTokenResponse("Bearer", "accessToken", Integer.MAX_VALUE, "refreshToken", Integer.MAX_VALUE, "email"); + private static final KakaoMember EXPECTED_KAKAO_MEMBER = new KakaoMember(1L, new KakaoAccount(true, true, true, "email")); @Nested class 로그인 { @Test void 카카오_로그인_페이지를_요청한다() { - var 응답 = 카카오_로그인_페이지_요청(); + var 응답 = 카카오_로그인_페이지_요청_API(); assertThat(응답.statusCode()).isEqualTo(OK.value()); } @Test - void 로그인을_한다() { + void 최초_로그인을_성공적으로_수행한다() { // given var kakaoTokenResponse = new KakaoTokenResponse("Bearer", "AccessToken", Integer.MAX_VALUE, "RefreshToken", Integer.MAX_VALUE, "email"); - when(kakaoOauthApiClient.requestToken(any())) - .thenReturn(kakaoTokenResponse); + when(kakaoOauthApiClient.requestToken(any())).thenReturn(kakaoTokenResponse); KakaoMember kakaoMember = new KakaoMember(1L, new KakaoAccount(true, true, true, "listywave@kakao.com")); - when(kakaoOauthApiClient.fetchKakaoMember(anyString())) - .thenReturn(kakaoMember); + when(kakaoOauthApiClient.fetchKakaoMember(anyString())).thenReturn(kakaoMember); // when - var 로그인_응답 = 로그인_요청(); - var 로그인_결과 = 로그인_응답.as(LoginResult.class); + var 로그인_응답 = 로그인_API(); + var 로그인_결과 = 로그인_응답.as(LoginResponse.class); // then assertAll( @@ -71,12 +67,16 @@ class 로그인 { () -> assertThat(로그인_결과.isFirst()).isEqualTo(true), () -> assertThat(jwtManager.readTokenWithPrefix("Bearer " + 로그인_결과.accessToken())).isEqualTo(1L), () -> assertThat(jwtManager.readTokenWithoutPrefix(로그인_결과.refreshToken())).isEqualTo(1L), - () -> assertThat(로그인_결과.profileImageUrl()).isIn(Arrays.stream(DefaultProfileImages.values()) - .map(DefaultProfileImages::getValue) - .toList()), - () -> assertThat(로그인_결과.backgroundImageUrl()).isIn(Arrays.stream(DefaultBackgroundImages.values()) - .map(DefaultBackgroundImages::getValue) - .toList()), + () -> assertThat(로그인_결과.profileImageUrl()).isIn( + Arrays.stream(DefaultProfileImages.values()) + .map(DefaultProfileImages::getValue) + .toList() + ), + () -> assertThat(로그인_결과.backgroundImageUrl()).isIn( + Arrays.stream(DefaultBackgroundImages.values()) + .map(DefaultBackgroundImages::getValue) + .toList() + ), () -> assertThat(로그인_결과.followingCount()).isEqualTo(0), () -> assertThat(로그인_결과.followerCount()).isEqualTo(0) ); @@ -85,11 +85,11 @@ class 로그인 { @Test void 로그인_시_이미_DB에_회원이_있으면_isFirst에_false를_반환한다() { // given - 로그인을_시도한다(expectedKakaoTokenResponse, expectedKakaoMember); + 로그인(EXPECTED_KAKAO_TOKEN_RESPONSE, EXPECTED_KAKAO_MEMBER); // when - var 로그인_응답 = 로그인을_시도한다(expectedKakaoTokenResponse, expectedKakaoMember); - var 로그인_결과 = 로그인_응답.as(LoginResult.class); + var 로그인_응답 = 로그인(EXPECTED_KAKAO_TOKEN_RESPONSE, EXPECTED_KAKAO_MEMBER); + var 로그인_결과 = 로그인_응답.as(LoginResponse.class); // then HTTP_상태_코드를_검증한다(로그인_응답, OK); @@ -97,54 +97,40 @@ class 로그인 { } @Test - void 로그인에_성공하면_Http_Body와_Cookie에_액세스_토큰과_리프레시_토큰을_담아_응답한다() { + void 로그인에_성공하면_Body와_Authorization헤더에_액세스_토큰과_리프레시_토큰을_담아_응답한다() { // when - var 로그인_응답 = 로그인을_시도한다(expectedKakaoTokenResponse, expectedKakaoMember); + var 로그인_응답 = 로그인(EXPECTED_KAKAO_TOKEN_RESPONSE, EXPECTED_KAKAO_MEMBER); var 로그인_결과 = 로그인_응답.as(LoginResponse.class); // then assertAll( - () -> assertThat(로그인_응답.cookie("accessToken")).isNotNull(), - () -> assertThat(로그인_응답.cookie("accessToken")).isNotBlank(), - () -> assertThat(로그인_응답.cookie("refreshToken")).isNotNull(), - () -> assertThat(로그인_응답.cookie("refreshToken")).isNotBlank(), - () -> assertThat(로그인_결과.accessToken()).isNotNull(), - () -> assertThat(로그인_결과.accessToken()).isNotBlank(), - () -> assertThat(로그인_결과.refreshToken()).isNotNull(), - () -> assertThat(로그인_결과.refreshToken()).isNotBlank() + () -> assertThat(로그인_결과.accessToken()).isNotNull().isNotBlank(), + () -> assertThat(로그인_결과.refreshToken()).isNotNull().isNotBlank() ); } + } - @Test - void Authorization_헤더에_리프레시_토큰을_담아_액세스_토큰을_재발급한다() { - // given - var 로그인_응답 = 로그인을_시도한다(expectedKakaoTokenResponse, expectedKakaoMember); - var 로그인한_사용자_ID = 로그인_응답.as(LoginResponse.class).id(); - var 리프레시_토큰 = jwtManager.createRefreshToken(로그인한_사용자_ID); - - // when - var 재발급_응답 = Authorization_헤더에_리프레시_토큰을_담아_액세스_토큰_재발급_요청(리프레시_토큰); - var 재발급된_액세스_토큰 = 재발급_응답.as(UpdateTokenResponse.class).accessToken(); - - // then - assertThat(jwtManager.readTokenWithoutPrefix(재발급_응답.cookie("accessToken"))).isEqualTo(로그인한_사용자_ID); - assertThat(jwtManager.readTokenWithoutPrefix(재발급된_액세스_토큰)).isEqualTo(로그인한_사용자_ID); - } + @Nested + class 액세스_토큰_재발급 { @Test - void Cookie에_리프레시_토큰을_담아_액세스_토큰을_재발급한다() { + void 리프레시_토큰으로_액세스_토큰을_재발급받는다() throws InterruptedException { // given - var 로그인_응답 = 로그인을_시도한다(expectedKakaoTokenResponse, expectedKakaoMember); - var 로그인한_사용자_ID = 로그인_응답.as(LoginResponse.class).id(); - var 리프레시_토큰 = jwtManager.createRefreshToken(로그인한_사용자_ID); + var 로그인_결과 = 로그인(EXPECTED_KAKAO_TOKEN_RESPONSE, EXPECTED_KAKAO_MEMBER); + var 로그인_응답값 = 로그인_결과.as(LoginResponse.class); + var 액세스_토큰 = 로그인_응답값.accessToken(); + var 리프레시_토큰 = 로그인_응답값.refreshToken(); // when - var 재발급_응답 = Cookie에_리프레시_토큰을_담아_액세스_토큰_재발급_요청(리프레시_토큰); - var 재발급된_액세스_토큰 = 재발급_응답.as(UpdateTokenResponse.class).accessToken(); + Thread.sleep(1000); // 동일한 시점에 액세스 토큰이 재발급되는 것을 방지하기 위한 딜레이 + var 토큰_재발급_결과 = 액세스_토큰_재발급_API(리프레시_토큰); + var 재발급된_액세스_토큰 = 토큰_재발급_결과.as(UpdateTokenResponse.class).accessToken(); // then - assertThat(jwtManager.readTokenWithoutPrefix(재발급_응답.cookie("accessToken"))).isEqualTo(로그인한_사용자_ID); - assertThat(jwtManager.readTokenWithoutPrefix(재발급된_액세스_토큰)).isEqualTo(로그인한_사용자_ID); + assertThat(재발급된_액세스_토큰) + .isNotNull() + .isNotBlank() + .isNotEqualTo(액세스_토큰); } } @@ -154,14 +140,13 @@ class 로그아웃 { @Test void 로그아웃을_한다() { // given - var 로그인_응답 = 로그인을_시도한다(expectedKakaoTokenResponse, expectedKakaoMember).as(LoginResult.class); + var 로그인_응답 = 로그인(EXPECTED_KAKAO_TOKEN_RESPONSE, EXPECTED_KAKAO_MEMBER).as(LoginResponse.class); - var kakaoLogoutResponse = new KakaoLogoutResponse(1L); - when(kakaoOauthApiClient.logout(anyString())) - .thenReturn(kakaoLogoutResponse); + var 로그아웃_결과_기대값 = new KakaoLogoutResponse(1L); + when(kakaoOauthApiClient.logout(anyString())).thenReturn(로그아웃_결과_기대값); // when - var 로그아웃_응답 = 로그아웃_요청(로그인_응답.accessToken()); + var 로그아웃_응답 = 로그아웃_API(로그인_응답.accessToken()); // then HTTP_상태_코드를_검증한다(로그아웃_응답, NO_CONTENT); @@ -174,33 +159,19 @@ class 회원_탈퇴 { @Test void 회원탈퇴를_한다() { // given - when(kakaoOauthApiClient.logout(anyString())) - .thenReturn(new KakaoLogoutResponse(1L)); - var 로그인_응답 = 로그인을_시도한다(expectedKakaoTokenResponse, expectedKakaoMember).as(LoginResult.class); + var 로그인_응답 = 로그인(EXPECTED_KAKAO_TOKEN_RESPONSE, EXPECTED_KAKAO_MEMBER).as(LoginResponse.class); // when - var 회원탈퇴_응답 = 회원탈퇴_요청(로그인_응답.accessToken()); + when(kakaoOauthApiClient.logout(anyString())).thenReturn(new KakaoLogoutResponse(1L)); + var 회원탈퇴_응답 = 회원탈퇴_API(로그인_응답.accessToken()); // then - HTTP_상태_코드를_검증한다(회원탈퇴_응답, NO_CONTENT); - } - - @Test - void 회원탈퇴를_한_사용자는_Soft_Delete_처리_된다() { - // given - when(kakaoOauthApiClient.logout(anyString())) - .thenReturn(new KakaoLogoutResponse(1L)); - var 로그인_결과 = 로그인을_시도한다(expectedKakaoTokenResponse, expectedKakaoMember).as(LoginResult.class); - - 회원탈퇴_요청(로그인_결과.accessToken()); - - // when - var 회원_정보_조회_응답 = 비회원_회원_정보_조회_요청(로그인_결과.id()); - var 회원_정보_조회_결과 = 회원_정보_조회_응답.as(ErrorResponse.class); - - // then - HTTP_상태_코드를_검증한다(회원_정보_조회_응답, BAD_REQUEST); - assertThat(회원_정보_조회_결과.code()).isEqualTo(DELETED_USER_EXCEPTION.name()); + var 회원조회_응답 = 비회원_회원_정보_조회_요청(로그인_응답.id()); + assertAll( + () -> HTTP_상태_코드를_검증한다(회원조회_응답, BAD_REQUEST), + () -> assertThat(회원조회_응답.body().as(ErrorResponse.class).code()).isEqualTo(DELETED_USER_EXCEPTION.name()), + () -> HTTP_상태_코드를_검증한다(회원탈퇴_응답, NO_CONTENT) + ); } } } diff --git a/src/test/java/com/listywave/acceptance/auth/AuthAcceptanceTestHelper.java b/src/test/java/com/listywave/acceptance/auth/AuthAcceptanceTestHelper.java index 13f1d545..131dae09 100644 --- a/src/test/java/com/listywave/acceptance/auth/AuthAcceptanceTestHelper.java +++ b/src/test/java/com/listywave/acceptance/auth/AuthAcceptanceTestHelper.java @@ -8,14 +8,14 @@ public abstract class AuthAcceptanceTestHelper { - public static ExtractableResponse 카카오_로그인_페이지_요청() { + public static ExtractableResponse 카카오_로그인_페이지_요청_API() { return given() .when().get("/auth/kakao") .then().log().all() .extract(); } - public static ExtractableResponse 로그인_요청() { + public static ExtractableResponse 로그인_API() { return given() .queryParam("code", "AuthCode") .when().get("/auth/redirect/kakao") @@ -23,7 +23,7 @@ public abstract class AuthAcceptanceTestHelper { .extract(); } - public static ExtractableResponse 로그아웃_요청(String accessToken) { + public static ExtractableResponse 로그아웃_API(String accessToken) { return given() .header(AUTHORIZATION, "Bearer " + accessToken) .when().patch("/auth/kakao") @@ -31,7 +31,7 @@ public abstract class AuthAcceptanceTestHelper { .extract(); } - public static ExtractableResponse 회원탈퇴_요청(String accessToken) { + public static ExtractableResponse 회원탈퇴_API(String accessToken) { return given() .header(AUTHORIZATION, "Bearer " + accessToken) .when().delete("/withdraw") @@ -39,19 +39,11 @@ public abstract class AuthAcceptanceTestHelper { .extract(); } - public static ExtractableResponse Authorization_헤더에_리프레시_토큰을_담아_액세스_토큰_재발급_요청(String refreshToken) { + public static ExtractableResponse 액세스_토큰_재발급_API(String refreshToken) { return given() .header(AUTHORIZATION, "Bearer " + refreshToken) .when().get("/auth/token") .then().log().all() .extract(); } - - public static ExtractableResponse Cookie에_리프레시_토큰을_담아_액세스_토큰_재발급_요청(String refreshToken) { - return given() - .cookie("refreshToken", refreshToken) - .when().get("/auth/token") - .then().log().all() - .extract(); - } } diff --git a/src/test/java/com/listywave/acceptance/common/AcceptanceTest.java b/src/test/java/com/listywave/acceptance/common/AcceptanceTest.java index 9f19352c..29d3cf27 100644 --- a/src/test/java/com/listywave/acceptance/common/AcceptanceTest.java +++ b/src/test/java/com/listywave/acceptance/common/AcceptanceTest.java @@ -11,13 +11,9 @@ import com.listywave.auth.infra.kakao.response.KakaoLogoutResponse; import com.listywave.auth.infra.kakao.response.KakaoMember; import com.listywave.auth.infra.kakao.response.KakaoTokenResponse; -import com.listywave.collaborator.repository.CollaboratorRepository; -import com.listywave.collection.repository.CollectionRepository; import com.listywave.list.application.domain.list.ListEntity; -import com.listywave.list.repository.ItemRepository; import com.listywave.list.repository.list.ListRepository; import com.listywave.user.application.domain.User; -import com.listywave.user.repository.follow.FollowRepository; import com.listywave.user.repository.user.UserRepository; import io.restassured.RestAssured; import io.restassured.response.ExtractableResponse; @@ -52,14 +48,6 @@ public abstract class AcceptanceTest { protected ListRepository listRepository; @Autowired protected JwtManager jwtManager; - @Autowired - protected CollectionRepository collectionRepository; - @Autowired - protected CollaboratorRepository collaboratorRepository; - @Autowired - protected ItemRepository itemRepository; - @Autowired - protected FollowRepository followRepository; @BeforeAll void beforeAll(@Autowired JdbcTemplate jdbcTemplate) { @@ -76,18 +64,18 @@ void setUp() { return userRepository.save(user); } - protected ExtractableResponse 로그인을_시도한다(KakaoTokenResponse expectedKakaoTokenResponse, KakaoMember expectedKakaoMember) { + protected ExtractableResponse 로그인(KakaoTokenResponse expectedKakaoTokenResponse, KakaoMember expectedKakaoMember) { when(kakaoOauthApiClient.requestToken(any())) .thenReturn(expectedKakaoTokenResponse); when(kakaoOauthApiClient.fetchKakaoMember(anyString())) .thenReturn(expectedKakaoMember); - return AuthAcceptanceTestHelper.로그인_요청(); + return AuthAcceptanceTestHelper.로그인_API(); } protected ExtractableResponse 회원_탈퇴(KakaoLogoutResponse expectedKakaoLogoutResponse, String accessToken) { when(kakaoOauthApiClient.logout(anyString())) .thenReturn(expectedKakaoLogoutResponse); - return AuthAcceptanceTestHelper.회원탈퇴_요청(accessToken); + return AuthAcceptanceTestHelper.회원탈퇴_API(accessToken); } protected String 액세스_토큰을_발급한다(User user) { diff --git a/src/test/java/com/listywave/auth/application/domain/JwtManagerTest.java b/src/test/java/com/listywave/auth/application/domain/JwtManagerTest.java index 41684bbc..f504490b 100644 --- a/src/test/java/com/listywave/auth/application/domain/JwtManagerTest.java +++ b/src/test/java/com/listywave/auth/application/domain/JwtManagerTest.java @@ -52,10 +52,13 @@ class 토큰을_발급한다 { @Test void userId로_액세스_토큰을_발급한다() { + // given Long userId = 312321L; + // when String result = jwtManager.createAccessToken(userId); + // then assertThat(result).isNotNull(); assertThat(result).isNotBlank(); } @@ -77,81 +80,85 @@ class 토큰을_읽는다 { private final Long userId = 1L; @Test - void PREFIX로_BEARER가_없는_경우_예외가_발생한다() { + void PREFIX로_BEARER가_있는_경우와_없는_경우_모두_읽을_수_있다() { + // given String accessToken = jwtManager.createAccessToken(userId); - CustomException exception = assertThrows(CustomException.class, () -> jwtManager.readTokenWithPrefix(accessToken)); + // when + Long result1 = jwtManager.readTokenWithPrefix("Bearer " + accessToken); + Long result2 = jwtManager.readTokenWithoutPrefix(accessToken); - assertThat(exception.getErrorCode()).isEqualTo(REQUIRED_ACCESS_TOKEN); + // then + assertThat(result1).isEqualTo(result2).isEqualTo(userId); } @ParameterizedTest @NullAndEmptySource void AccessToken_값이_빈_공백이거나_null이면_예외가_발생한다(String accessToken) { + // when CustomException exception = assertThrows(CustomException.class, () -> jwtManager.readTokenWithPrefix(accessToken)); + // then assertThat(exception.getErrorCode()).isEqualTo(REQUIRED_ACCESS_TOKEN); } - @Test - void 액세스_토큰을_읽고_userId를_반환한다() { - String accessToken = jwtManager.createAccessToken(userId); - - Long result = jwtManager.readTokenWithPrefix("Bearer " + accessToken); - - assertThat(result).isEqualTo(userId); - } - @Test void 유효기간이_지난_액세스_토큰인_경우_예외가_발생한다() { + // given JwtManager jwtManager = new JwtManager(plainSecretKey, issuer, accessTokenValidTimeDuration, refreshTokenValidTimeDuration, NANOSECONDS, refreshTokenValidTimeUnit); String accessToken = jwtManager.createAccessToken(userId); + // when + // then assertThatThrownBy(() -> jwtManager.readTokenWithPrefix("Bearer " + accessToken)) .isInstanceOf(ExpiredJwtException.class); } @Test void SecretKey가_다른_액세스_토큰인_경우_예외가_발생한다() { + // given String accessToken = jwtManager.createAccessToken(userId); + // when JwtManager differentSecretKeyJwtManager = new JwtManager("sfaksdlhfdsakjfhasdkfuhcasknfuhsabkdvajnfcgsankzdjhfc", issuer, 1, 1, NANOSECONDS, NANOSECONDS); + // then assertThatThrownBy(() -> differentSecretKeyJwtManager.readTokenWithPrefix("Bearer " + accessToken)) .isInstanceOf(SignatureException.class); } - @Test - void 리프레시_토큰을_읽고_userId를_반환한다() { - String refreshToken = jwtManager.createRefreshToken(userId); - - assertThat(refreshToken).isNotNull(); - assertThat(refreshToken).isNotBlank(); - } - @ParameterizedTest @NullAndEmptySource void 리프레시_토큰이_null이거나_공백이면_예외를_발생한다(String refreshToken) { + // when + // then assertThatThrownBy(() -> jwtManager.readTokenWithoutPrefix(refreshToken)); } @Test void 유효기간이_지난_리프레시_토큰인_경우_예외가_발생한다() { + // given JwtManager jwtManager = new JwtManager(plainSecretKey, issuer, 1, 1, NANOSECONDS, NANOSECONDS); + + // when String refreshToken = jwtManager.createRefreshToken(userId); + // then assertThatThrownBy(() -> jwtManager.readTokenWithoutPrefix(refreshToken)) .isInstanceOf(ExpiredJwtException.class); } @Test void SecretKey가_다른_리프레시_토큰인_경우_예외가_발생한다() { + // given String refreshToken = jwtManager.createRefreshToken(userId); + // when JwtManager otherSecretKeyJwtManager = new JwtManager( "sdfklasdhfisdhfisadfhsicalndufhasdukhxaukdsadfuhsakcnfsajfhasdjkfhx", issuer, 1, 1, MINUTES, MINUTES); + // then assertThatThrownBy(() -> otherSecretKeyJwtManager.readTokenWithoutPrefix(refreshToken)) .isInstanceOf(SignatureException.class); } diff --git a/src/test/java/com/listywave/common/auth/AuthorizationInterceptorTest.java b/src/test/java/com/listywave/common/auth/AuthorizationInterceptorTest.java index baf14609..5ce3ef14 100644 --- a/src/test/java/com/listywave/common/auth/AuthorizationInterceptorTest.java +++ b/src/test/java/com/listywave/common/auth/AuthorizationInterceptorTest.java @@ -8,7 +8,6 @@ import static org.springframework.http.HttpMethod.OPTIONS; import com.listywave.auth.application.domain.JwtManager; -import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.BeforeEach; @@ -24,7 +23,6 @@ class AuthorizationInterceptorTest { private JwtManager jwtManager; private AuthContext authContext; - private TokenReader tokenReader; private AuthorizationInterceptor authorizationInterceptor; private HttpServletRequest httpServletRequest; private HttpServletResponse httpServletResponse; @@ -33,14 +31,13 @@ class AuthorizationInterceptorTest { void setUp() { jwtManager = mock(JwtManager.class); authContext = new AuthContext(); - tokenReader = new TokenReader(jwtManager); - authorizationInterceptor = new AuthorizationInterceptor(authContext, tokenReader); + authorizationInterceptor = new AuthorizationInterceptor(jwtManager, authContext); httpServletRequest = mock(HttpServletRequest.class); httpServletResponse = mock(HttpServletResponse.class); } @Test - void 인증이_필요하지_않은_리소스에_접근할_때는_아무_처리를_하지_않는다() throws Exception { + void 인증이_필요하지_않은_리소스에_접근할_때는_아무_처리를_하지_않는다() { // given when(httpServletRequest.getMethod()).thenReturn(GET.name()); HandlerMethod handler = mock(HandlerMethod.class); @@ -59,7 +56,7 @@ void setUp() { } @Test - void Authorization_헤더에_담긴_액세스_토큰을_읽어_AuthContext에_userId를_저장한다() throws Exception { + void Authorization_헤더에_담긴_액세스_토큰을_읽어_AuthContext에_userId를_저장한다() { // given when(httpServletRequest.getMethod()).thenReturn(GET.name()); HandlerMethod handler = mock(HandlerMethod.class); @@ -81,31 +78,7 @@ void setUp() { } @Test - void 액세스_토큰이_Authorization_헤더가_아닌_Cookie에_담기면_쿠키값을_읽어_AuthContext에_userId를_저장한다() throws Exception { - // given - when(httpServletRequest.getMethod()).thenReturn(GET.name()); - HandlerMethod handler = mock(HandlerMethod.class); - - RequestMapping requestMapping = mock(RequestMapping.class); - when(handler.getMethodAnnotation(RequestMapping.class)).thenReturn(requestMapping); - when(requestMapping.value()).thenReturn(new String[]{"my"}); - when(requestMapping.method()).thenReturn(new RequestMethod[]{RequestMethod.GET}); - - String accessToken = "dufhaslndckhfksdjhkscjghacs.dkjfhnxasjdfhsjh.snvkjghc"; - when(httpServletRequest.getHeader(AUTHORIZATION)).thenReturn(null); - Cookie[] cookies = {new Cookie("accessToken", accessToken)}; - when(httpServletRequest.getCookies()).thenReturn(cookies); - when(jwtManager.readTokenWithoutPrefix(accessToken)).thenReturn(1L); - - // when - authorizationInterceptor.preHandle(httpServletRequest, httpServletResponse, handler); - - // then - assertThat(authContext.getUserId()).isEqualTo(1L); - } - - @Test - void 요청_메서드가_OPTIONS_이면_값을_읽지_않는다() throws Exception { + void 요청_메서드가_OPTIONS이면_값을_읽지_않는다() { // given when(httpServletRequest.getMethod()).thenReturn(OPTIONS.name()); @@ -117,7 +90,7 @@ void setUp() { } @Test - void 핸들러_타입이_HandlerMethod가_아니면_값을_읽지_않는다() throws Exception { + void 핸들러_타입이_HandlerMethod가_아니면_값을_읽지_않는다() { // given when(httpServletRequest.getMethod()).thenReturn(GET.name()); diff --git a/src/test/java/com/listywave/common/auth/TokenReaderTest.java b/src/test/java/com/listywave/common/auth/TokenReaderTest.java deleted file mode 100644 index b62bb978..00000000 --- a/src/test/java/com/listywave/common/auth/TokenReaderTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.listywave.common.auth; - -import static java.util.concurrent.TimeUnit.HOURS; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.springframework.http.HttpHeaders.AUTHORIZATION; - -import com.listywave.auth.application.domain.JwtManager; -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -@DisplayName("TokenReader는 ") -class TokenReaderTest { - - private final JwtManager jwtManager = new JwtManager( - "skfhcnksduhfslkuhvfbsliduchsiludbhfsulicfhbsukchsldshdk", - "http://localhost.com", - 1, - 1, - HOURS, - HOURS - ); - private final TokenReader tokenReader = new TokenReader(jwtManager); - private final HttpServletRequest httpServletRequest = mock(HttpServletRequest.class); - - @Test - void Authorization_헤더에_있는_토큰_값이_있으면_그_값을_읽는다() { - // given - String accessToken = jwtManager.createAccessToken(1L); - when(httpServletRequest.getHeader(AUTHORIZATION)).thenReturn("Bearer " + accessToken); - - // when - Long result = tokenReader.readAccessToken(httpServletRequest); - - // then - assertThat(result).isEqualTo(1L); - } - - @Test - void Authorization_헤더가_아닌_쿠키에_토큰_값이_있으면_그_값을_읽는다() { - // given - String accessToken = jwtManager.createAccessToken(2L); - when(httpServletRequest.getCookies()) - .thenReturn(new Cookie[]{new Cookie("accessToken", accessToken)}); - - // when - Long result = tokenReader.readAccessToken(httpServletRequest); - - // then - assertThat(result).isEqualTo(2L); - } - - @Test - void 헤더와_쿠키_모두_토큰_값이_없으면_null을_반환한다() { - // given - when(httpServletRequest.getHeader(AUTHORIZATION)).thenReturn(null); - when(httpServletRequest.getCookies()).thenReturn(new Cookie[]{}); - - // when - Long result = tokenReader.readAccessToken(httpServletRequest); - - // then - assertThat(result).isNull(); - } -} From 8b8e7a7a83ce2ec28962d76e9a6bf14e59eb3b62 Mon Sep 17 00:00:00 2001 From: kdkdhoho Date: Sun, 1 Sep 2024 14:22:38 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=20feat:=20=EB=A1=9C=EC=BB=AC=EC=9A=A9=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B0=9C=EC=84=A0=20(#282)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../listywave/auth/dev/domain/DevAccount.java | 47 ++++++++++ .../dev/presentation/DevAuthController.java | 39 +++++++++ .../dev/presentation/LocalLoginRequest.java | 7 ++ .../dev/repository/DevAccountRepository.java | 12 +++ .../presentation/LocalAuthController.java | 85 ------------------- .../exception/GlobalExceptionHandler.java | 4 +- 6 files changed, 107 insertions(+), 87 deletions(-) create mode 100644 src/main/java/com/listywave/auth/dev/domain/DevAccount.java create mode 100644 src/main/java/com/listywave/auth/dev/presentation/DevAuthController.java create mode 100644 src/main/java/com/listywave/auth/dev/presentation/LocalLoginRequest.java create mode 100644 src/main/java/com/listywave/auth/dev/repository/DevAccountRepository.java delete mode 100644 src/main/java/com/listywave/auth/presentation/LocalAuthController.java diff --git a/src/main/java/com/listywave/auth/dev/domain/DevAccount.java b/src/main/java/com/listywave/auth/dev/domain/DevAccount.java new file mode 100644 index 00000000..9d3dd06d --- /dev/null +++ b/src/main/java/com/listywave/auth/dev/domain/DevAccount.java @@ -0,0 +1,47 @@ +package com.listywave.auth.dev.domain; + +import static jakarta.persistence.FetchType.LAZY; + +import com.listywave.user.application.domain.User; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.MapsId; +import jakarta.persistence.OneToOne; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class DevAccount { + + @Id + @Column(name = "user_id", nullable = false, unique = true) + private Long id; + + @MapsId + @OneToOne(fetch = LAZY) + @JoinColumn(name = "user_id", nullable = false, unique = true) + private User user; + + @Column(nullable = false, unique = true) + private String account; + + @Column(nullable = false) + private String password; + + public void validatePassword(String password) { + if (this.password.equals(password)) { + return; + } + throw new IllegalArgumentException("비밀번호가 틀렸습니다."); + } + + public Long getUserId() { + return user.getId(); + } +} diff --git a/src/main/java/com/listywave/auth/dev/presentation/DevAuthController.java b/src/main/java/com/listywave/auth/dev/presentation/DevAuthController.java new file mode 100644 index 00000000..b43753d1 --- /dev/null +++ b/src/main/java/com/listywave/auth/dev/presentation/DevAuthController.java @@ -0,0 +1,39 @@ +package com.listywave.auth.dev.presentation; + +import com.listywave.auth.application.domain.JwtManager; +import com.listywave.auth.application.dto.LoginResponse; +import com.listywave.auth.dev.domain.DevAccount; +import com.listywave.auth.dev.repository.DevAccountRepository; +import com.listywave.user.application.domain.User; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Profile; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@Profile("!prod") +@RequiredArgsConstructor +public class DevAuthController { + + private final JwtManager jwtManager; + private final DevAccountRepository devAccountRepository; + + @PostMapping("/login/local") + ResponseEntity localLogin(@RequestBody LocalLoginRequest request) { + String account = request.account(); + String password = request.password(); + + Optional optional = devAccountRepository.findByAccount(account); + DevAccount devAccount = optional.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 계정입니다.")); + + devAccount.validatePassword(password); + User user = devAccount.getUser(); + String accessToken = jwtManager.createAccessToken(user.getId()); + String refreshToken = jwtManager.createRefreshToken(user.getId()); + LoginResponse response = LoginResponse.of(user, accessToken, refreshToken, false); + return ResponseEntity.ok(response); + } +} diff --git a/src/main/java/com/listywave/auth/dev/presentation/LocalLoginRequest.java b/src/main/java/com/listywave/auth/dev/presentation/LocalLoginRequest.java new file mode 100644 index 00000000..054b82ed --- /dev/null +++ b/src/main/java/com/listywave/auth/dev/presentation/LocalLoginRequest.java @@ -0,0 +1,7 @@ +package com.listywave.auth.dev.presentation; + +public record LocalLoginRequest( + String account, + String password +) { +} diff --git a/src/main/java/com/listywave/auth/dev/repository/DevAccountRepository.java b/src/main/java/com/listywave/auth/dev/repository/DevAccountRepository.java new file mode 100644 index 00000000..ae293253 --- /dev/null +++ b/src/main/java/com/listywave/auth/dev/repository/DevAccountRepository.java @@ -0,0 +1,12 @@ +package com.listywave.auth.dev.repository; + +import com.listywave.auth.dev.domain.DevAccount; +import java.util.Optional; +import org.springframework.context.annotation.Profile; +import org.springframework.data.jpa.repository.JpaRepository; + +@Profile("!prod") +public interface DevAccountRepository extends JpaRepository { + + Optional findByAccount(String account); +} diff --git a/src/main/java/com/listywave/auth/presentation/LocalAuthController.java b/src/main/java/com/listywave/auth/presentation/LocalAuthController.java deleted file mode 100644 index 1cef335b..00000000 --- a/src/main/java/com/listywave/auth/presentation/LocalAuthController.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.listywave.auth.presentation; - -import static com.listywave.common.exception.ErrorCode.INVALID_ACCESS; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.springframework.http.HttpHeaders.SET_COOKIE; - -import com.listywave.auth.application.domain.JwtManager; -import com.listywave.auth.presentation.dto.LoginResponse; -import com.listywave.common.exception.CustomException; -import com.listywave.common.util.TimeUtils; -import com.listywave.user.application.domain.User; -import com.listywave.user.repository.user.UserRepository; -import java.time.Duration; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Profile; -import org.springframework.http.ResponseCookie; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@Profile("!prod") -@RequiredArgsConstructor -public class LocalAuthController { - - private final UserRepository userRepository; - private final JwtManager jwtManager; - - @Value("${local-login.id}") - private String id; - @Value("${local-login.password}") - private String password; - - @GetMapping("/login/local") - ResponseEntity localLogin( - @RequestParam(name = "id") String id, - @RequestParam(name = "password") String password - ) { - if (this.id.equals(id) && this.password.equals(password)) { - User user = userRepository.getById(1L); - - String accessToken = jwtManager.createAccessToken(user.getId()); - String refreshToken = jwtManager.createRefreshToken(user.getId()); - - ResponseCookie accessTokenCookie = createCookie( - "accessToken", - accessToken, - Duration.ofSeconds(TimeUtils.convertTimeUnit( - jwtManager.getAccessTokenValidTimeDuration(), - jwtManager.getAccessTokenValidTimeUnit(), - SECONDS - )) - ); - ResponseCookie refreshTokenCookie = createCookie( - "refreshToken", - refreshToken, - Duration.ofSeconds(TimeUtils.convertTimeUnit( - jwtManager.getRefreshTokenValidTimeDuration(), - jwtManager.getRefreshTokenValidTimeUnit(), - SECONDS - )) - ); - - return ResponseEntity.ok() - .header(SET_COOKIE, accessTokenCookie.toString()) - .header(SET_COOKIE, refreshTokenCookie.toString()) - .body(LoginResponse.of(user, accessToken, refreshToken)); - } - throw new CustomException(INVALID_ACCESS); - } - - private ResponseCookie createCookie(String name, String value, Duration maxAge) { - return ResponseCookie.from(name) - .value(value) - .maxAge(maxAge) - .domain("dev.api.listywave.com") - .path("/") - .httpOnly(false) - .secure(true) - .sameSite("None") - .build(); - } -} diff --git a/src/main/java/com/listywave/common/exception/GlobalExceptionHandler.java b/src/main/java/com/listywave/common/exception/GlobalExceptionHandler.java index 301a1c63..77f1b300 100644 --- a/src/main/java/com/listywave/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/listywave/common/exception/GlobalExceptionHandler.java @@ -29,9 +29,9 @@ ResponseEntity handleCustomException(CustomException e) { } @ExceptionHandler(Exception.class) - protected ResponseEntity handleException(Exception e) { + protected ResponseEntity handleException(Exception e) { log.error("[InternalServerError] : {}", e.getMessage(), e); - return ResponseEntity.status(INTERNAL_SERVER_ERROR).build(); + return ResponseEntity.status(INTERNAL_SERVER_ERROR).body(e.getMessage()); } @ExceptionHandler(MethodArgumentTypeMismatchException.class) From bcb41b623b7d94deadf2bd8012ebd14d586cb171 Mon Sep 17 00:00:00 2001 From: kdkdhoho Date: Mon, 2 Sep 2024 13:44:51 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=20refactor:=20IllegalArgumentException=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=ED=95=B8=EB=93=A4=EB=9F=AC=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80=20(#282)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../listywave/common/exception/GlobalExceptionHandler.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/com/listywave/common/exception/GlobalExceptionHandler.java b/src/main/java/com/listywave/common/exception/GlobalExceptionHandler.java index 77f1b300..892a1261 100644 --- a/src/main/java/com/listywave/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/listywave/common/exception/GlobalExceptionHandler.java @@ -10,6 +10,7 @@ import io.jsonwebtoken.security.SignatureException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -34,6 +35,12 @@ protected ResponseEntity handleException(Exception e) { return ResponseEntity.status(INTERNAL_SERVER_ERROR).body(e.getMessage()); } + @ExceptionHandler(IllegalArgumentException.class) + ResponseEntity handleIllegalArgumentException(IllegalArgumentException e) { + log.error("[IllegalArgumentException] : {}", e.getMessage(), e); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); + } + @ExceptionHandler(MethodArgumentTypeMismatchException.class) ResponseEntity handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) { log.error("[MethodArgumentTypeMismatchException] : {}", e.getMessage(), e); From f876bfdc1cdda5c7fe5e1d52c3ef59f1b2bfc9b8 Mon Sep 17 00:00:00 2001 From: kdkdhoho Date: Mon, 2 Sep 2024 13:46:47 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=20test:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95=20(#282)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/listywave/acceptance/auth/AuthAcceptanceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/listywave/acceptance/auth/AuthAcceptanceTest.java b/src/test/java/com/listywave/acceptance/auth/AuthAcceptanceTest.java index 4df62d50..2d1873e5 100644 --- a/src/test/java/com/listywave/acceptance/auth/AuthAcceptanceTest.java +++ b/src/test/java/com/listywave/acceptance/auth/AuthAcceptanceTest.java @@ -97,7 +97,7 @@ class 로그인 { } @Test - void 로그인에_성공하면_Body와_Authorization헤더에_액세스_토큰과_리프레시_토큰을_담아_응답한다() { + void 로그인에_성공하면_Body에_액세스_토큰과_리프레시_토큰을_담아_응답한다() { // when var 로그인_응답 = 로그인(EXPECTED_KAKAO_TOKEN_RESPONSE, EXPECTED_KAKAO_MEMBER); var 로그인_결과 = 로그인_응답.as(LoginResponse.class);