From 038b9ed636b32e513904fad3e9a8f4736d4952f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Mon, 8 Jan 2024 19:30:01 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20logout=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koreatech/koin/domain/auth/UserAuth.java | 14 +++++ .../auth/resolver/UserArgumentResolver.java | 48 +++++++++++++++++ .../user/controller/UserController.java | 20 ++++--- .../user/repository/UserTokenRepository.java | 6 +-- .../koin/domain/user/service/UserService.java | 21 ++++---- .../koin/global/config/WebConfig.java | 3 ++ .../koin/acceptance/AuthApiTest.java | 53 +++++++++++++++++++ 7 files changed, 146 insertions(+), 19 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/auth/UserAuth.java create mode 100644 src/main/java/in/koreatech/koin/domain/auth/resolver/UserArgumentResolver.java diff --git a/src/main/java/in/koreatech/koin/domain/auth/UserAuth.java b/src/main/java/in/koreatech/koin/domain/auth/UserAuth.java new file mode 100644 index 000000000..ec28859f6 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/auth/UserAuth.java @@ -0,0 +1,14 @@ +package in.koreatech.koin.domain.auth; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Target({PARAMETER, FIELD}) +@Retention(RUNTIME) +public @interface UserAuth { + +} diff --git a/src/main/java/in/koreatech/koin/domain/auth/resolver/UserArgumentResolver.java b/src/main/java/in/koreatech/koin/domain/auth/resolver/UserArgumentResolver.java new file mode 100644 index 000000000..d44e59c78 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/auth/resolver/UserArgumentResolver.java @@ -0,0 +1,48 @@ +package in.koreatech.koin.domain.auth.resolver; + +import in.koreatech.koin.domain.auth.JwtProvider; +import in.koreatech.koin.domain.auth.UserAuth; +import in.koreatech.koin.domain.auth.exception.AuthException; +import in.koreatech.koin.domain.user.exception.UserNotFoundException; +import in.koreatech.koin.domain.user.repository.UserRepository; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +@Component +@RequiredArgsConstructor +public class UserArgumentResolver implements HandlerMethodArgumentResolver { + + private static final String AUTHORIZATION = "Authorization"; + + private final JwtProvider jwtProvider; + private final UserRepository userRepository; + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(UserAuth.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + + HttpServletRequest nativeRequest = webRequest.getNativeRequest(HttpServletRequest.class); + if (nativeRequest == null) { + throw new AuthException("요청 값이 비어있습니다."); + } + + String authorizationHeader = nativeRequest.getHeader(AUTHORIZATION); + if (authorizationHeader == null) { + throw new AuthException("인증 헤더값이 비어있습니다. authorizationHeader: " + nativeRequest); + } + Long userId = jwtProvider.getUserId(authorizationHeader); + return userRepository.findById(userId) + .orElseThrow(() -> UserNotFoundException.withDetail("authorizationHeader: " + authorizationHeader)); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/user/controller/UserController.java b/src/main/java/in/koreatech/koin/domain/user/controller/UserController.java index d8cd202af..a98dac507 100644 --- a/src/main/java/in/koreatech/koin/domain/user/controller/UserController.java +++ b/src/main/java/in/koreatech/koin/domain/user/controller/UserController.java @@ -1,17 +1,17 @@ package in.koreatech.koin.domain.user.controller; -import java.net.URI; - -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; - +import in.koreatech.koin.domain.auth.UserAuth; import in.koreatech.koin.domain.user.dto.UserLoginRequest; import in.koreatech.koin.domain.user.dto.UserLoginResponse; +import in.koreatech.koin.domain.user.model.User; import in.koreatech.koin.domain.user.service.UserService; import jakarta.validation.Valid; +import java.net.URI; import lombok.RequiredArgsConstructor; +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 @RequiredArgsConstructor @@ -25,4 +25,10 @@ public ResponseEntity login(@RequestBody @Valid UserLoginRequ return ResponseEntity.created(URI.create("/")) .body(response); } + + @PostMapping("/user/logout") + public ResponseEntity logout(@UserAuth User user) { + userService.logout(user); + return ResponseEntity.ok().build(); + } } diff --git a/src/main/java/in/koreatech/koin/domain/user/repository/UserTokenRepository.java b/src/main/java/in/koreatech/koin/domain/user/repository/UserTokenRepository.java index 562674d81..1af8fdd11 100644 --- a/src/main/java/in/koreatech/koin/domain/user/repository/UserTokenRepository.java +++ b/src/main/java/in/koreatech/koin/domain/user/repository/UserTokenRepository.java @@ -1,14 +1,14 @@ package in.koreatech.koin.domain.user.repository; +import in.koreatech.koin.domain.user.model.UserToken; import java.util.Optional; - import org.springframework.data.repository.Repository; -import in.koreatech.koin.domain.user.model.UserToken; - public interface UserTokenRepository extends Repository { UserToken save(UserToken userToken); Optional findById(Long userId); + + void deleteById(Long id); } diff --git a/src/main/java/in/koreatech/koin/domain/user/service/UserService.java b/src/main/java/in/koreatech/koin/domain/user/service/UserService.java index 1722a93a6..d5a6e427c 100644 --- a/src/main/java/in/koreatech/koin/domain/user/service/UserService.java +++ b/src/main/java/in/koreatech/koin/domain/user/service/UserService.java @@ -1,20 +1,18 @@ package in.koreatech.koin.domain.user.service; -import in.koreatech.koin.domain.user.exception.UserNotFoundException; -import java.time.LocalDateTime; -import java.util.UUID; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import in.koreatech.koin.domain.auth.JwtProvider; -import in.koreatech.koin.domain.user.model.User; -import in.koreatech.koin.domain.user.model.UserToken; import in.koreatech.koin.domain.user.dto.UserLoginRequest; import in.koreatech.koin.domain.user.dto.UserLoginResponse; +import in.koreatech.koin.domain.user.exception.UserNotFoundException; +import in.koreatech.koin.domain.user.model.User; +import in.koreatech.koin.domain.user.model.UserToken; import in.koreatech.koin.domain.user.repository.UserRepository; import in.koreatech.koin.domain.user.repository.UserTokenRepository; +import java.time.LocalDateTime; +import java.util.UUID; import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -42,4 +40,9 @@ public UserLoginResponse login(UserLoginRequest request) { return UserLoginResponse.of(accessToken, savedToken.getRefreshToken(), saved.getUserType().getValue()); } + + @Transactional + public void logout(User user) { + userTokenRepository.deleteById(user.getId()); + } } diff --git a/src/main/java/in/koreatech/koin/global/config/WebConfig.java b/src/main/java/in/koreatech/koin/global/config/WebConfig.java index fc3c6f95e..9fee48067 100644 --- a/src/main/java/in/koreatech/koin/global/config/WebConfig.java +++ b/src/main/java/in/koreatech/koin/global/config/WebConfig.java @@ -1,6 +1,7 @@ package in.koreatech.koin.global.config; import in.koreatech.koin.domain.auth.resolver.StudentArgumentResolver; +import in.koreatech.koin.domain.auth.resolver.UserArgumentResolver; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; @@ -11,10 +12,12 @@ @RequiredArgsConstructor public class WebConfig implements WebMvcConfigurer { + private final UserArgumentResolver userArgumentResolver; private final StudentArgumentResolver studentArgumentResolver; @Override public void addArgumentResolvers(final List resolvers) { + resolvers.add(userArgumentResolver); resolvers.add(studentArgumentResolver); } } diff --git a/src/test/java/in/koreatech/koin/acceptance/AuthApiTest.java b/src/test/java/in/koreatech/koin/acceptance/AuthApiTest.java index c3f4073fb..e4befb0f1 100644 --- a/src/test/java/in/koreatech/koin/acceptance/AuthApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/AuthApiTest.java @@ -12,6 +12,8 @@ import io.restassured.http.ContentType; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; +import java.util.Optional; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -73,4 +75,55 @@ void userLoginSuccess() { } ); } + + @Test + @DisplayName("사용자가 로그인 이후 로그아웃을 수행한다") + void userLogoutSuccess() { + User user = User.builder() + .password("1234") + .nickname("주노") + .name("최준호") + .phoneNumber("010-1234-5678") + .userType(UserType.USER) + .email("test@koreatech.ac.kr") + .isAuthed(true) + .isDeleted(false) + .build(); + + userRepository.save(user); + + ExtractableResponse response = RestAssured + .given() + .log().all() + .body(""" + { + "email": "test@koreatech.ac.kr", + "password": "1234" + } + """) + .contentType(ContentType.JSON) + .when() + .log().all() + .post("/user/login") + .then() + .log().all() + .statusCode(HttpStatus.CREATED.value()) + .extract(); + + RestAssured + .given() + .log().all() + .header("Authorization", "BEARER " + response.jsonPath().getString("token")) + .when() + .log().all() + .post("/user/logout") + .then() + .log().all() + .statusCode(HttpStatus.OK.value()) + .extract(); + + Optional token = tokenRepository.findById(user.getId()); + + Assertions.assertThat(token).isEmpty(); + } } From c2709da235f14cff3c848b8ded3c04bf83fc292e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Tue, 9 Jan 2024 14:59:26 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20accessToken=20=EC=9E=AC=EB=B0=9C?= =?UTF-8?q?=EA=B8=89=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/UserController.java | 11 ++++ .../user/dto/UserTokenRefreshRequest.java | 12 +++++ .../user/dto/UserTokenRefreshResponse.java | 13 +++++ .../koin/domain/user/service/UserService.java | 13 +++++ .../koin/acceptance/AuthApiTest.java | 51 +++++++++++++++++++ 5 files changed, 100 insertions(+) create mode 100644 src/main/java/in/koreatech/koin/domain/user/dto/UserTokenRefreshRequest.java create mode 100644 src/main/java/in/koreatech/koin/domain/user/dto/UserTokenRefreshResponse.java diff --git a/src/main/java/in/koreatech/koin/domain/user/controller/UserController.java b/src/main/java/in/koreatech/koin/domain/user/controller/UserController.java index a98dac507..7df7b7728 100644 --- a/src/main/java/in/koreatech/koin/domain/user/controller/UserController.java +++ b/src/main/java/in/koreatech/koin/domain/user/controller/UserController.java @@ -3,6 +3,8 @@ import in.koreatech.koin.domain.auth.UserAuth; import in.koreatech.koin.domain.user.dto.UserLoginRequest; import in.koreatech.koin.domain.user.dto.UserLoginResponse; +import in.koreatech.koin.domain.user.dto.UserTokenRefreshRequest; +import in.koreatech.koin.domain.user.dto.UserTokenRefreshResponse; import in.koreatech.koin.domain.user.model.User; import in.koreatech.koin.domain.user.service.UserService; import jakarta.validation.Valid; @@ -31,4 +33,13 @@ public ResponseEntity logout(@UserAuth User user) { userService.logout(user); return ResponseEntity.ok().build(); } + + @PostMapping("/user/refresh") + public ResponseEntity refresh( + @UserAuth User user, + @RequestBody @Valid UserTokenRefreshRequest request + ) { + UserTokenRefreshResponse tokenGroupResponse = userService.refresh(user, request); + return ResponseEntity.ok().body(tokenGroupResponse); + } } diff --git a/src/main/java/in/koreatech/koin/domain/user/dto/UserTokenRefreshRequest.java b/src/main/java/in/koreatech/koin/domain/user/dto/UserTokenRefreshRequest.java new file mode 100644 index 000000000..cbe02da56 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/user/dto/UserTokenRefreshRequest.java @@ -0,0 +1,12 @@ +package in.koreatech.koin.domain.user.dto; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import jakarta.validation.constraints.NotNull; + +@JsonNaming(value = SnakeCaseStrategy.class) +public record UserTokenRefreshRequest( + @NotNull(message = "refresh_token을 입력해주세요.") String refreshToken +) { + +} diff --git a/src/main/java/in/koreatech/koin/domain/user/dto/UserTokenRefreshResponse.java b/src/main/java/in/koreatech/koin/domain/user/dto/UserTokenRefreshResponse.java new file mode 100644 index 000000000..86c7f793f --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/user/dto/UserTokenRefreshResponse.java @@ -0,0 +1,13 @@ +package in.koreatech.koin.domain.user.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record UserTokenRefreshResponse( + @JsonProperty("token") String accessToken, + @JsonProperty("refresh_token") String refreshToken +) { + + public static UserTokenRefreshResponse of(String accessToken, String refreshToken) { + return new UserTokenRefreshResponse(accessToken, refreshToken); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/user/service/UserService.java b/src/main/java/in/koreatech/koin/domain/user/service/UserService.java index d5a6e427c..22a5ed296 100644 --- a/src/main/java/in/koreatech/koin/domain/user/service/UserService.java +++ b/src/main/java/in/koreatech/koin/domain/user/service/UserService.java @@ -3,12 +3,15 @@ import in.koreatech.koin.domain.auth.JwtProvider; import in.koreatech.koin.domain.user.dto.UserLoginRequest; import in.koreatech.koin.domain.user.dto.UserLoginResponse; +import in.koreatech.koin.domain.user.dto.UserTokenRefreshRequest; +import in.koreatech.koin.domain.user.dto.UserTokenRefreshResponse; import in.koreatech.koin.domain.user.exception.UserNotFoundException; import in.koreatech.koin.domain.user.model.User; import in.koreatech.koin.domain.user.model.UserToken; import in.koreatech.koin.domain.user.repository.UserRepository; import in.koreatech.koin.domain.user.repository.UserTokenRepository; import java.time.LocalDateTime; +import java.util.Objects; import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -45,4 +48,14 @@ public UserLoginResponse login(UserLoginRequest request) { public void logout(User user) { userTokenRepository.deleteById(user.getId()); } + + public UserTokenRefreshResponse refresh(User user, UserTokenRefreshRequest request) { + UserToken userToken = userTokenRepository.findById(user.getId()) + .orElseThrow(() -> new IllegalArgumentException("refresh token이 존재하지 않습니다. request: " + request)); + if (Objects.equals(userToken.getRefreshToken(), request.refreshToken())) { + throw new IllegalArgumentException("refresh token이 일치하지 않습니다. request: " + request); + } + String accessToken = jwtProvider.createToken(user); + return UserTokenRefreshResponse.of(accessToken, userToken.getRefreshToken()); + } } diff --git a/src/test/java/in/koreatech/koin/acceptance/AuthApiTest.java b/src/test/java/in/koreatech/koin/acceptance/AuthApiTest.java index e4befb0f1..4e46b3bca 100644 --- a/src/test/java/in/koreatech/koin/acceptance/AuthApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/AuthApiTest.java @@ -126,4 +126,55 @@ void userLogoutSuccess() { Assertions.assertThat(token).isEmpty(); } + + @Test + @DisplayName("사용자가 로그인 이후 refreshToken을 재발급한다") + void userRefreshToken() { + User user = User.builder() + .password("1234") + .nickname("주노") + .name("최준호") + .phoneNumber("010-1234-5678") + .userType(UserType.USER) + .email("test@koreatech.ac.kr") + .isAuthed(true) + .isDeleted(false) + .build(); + + userRepository.save(user); + + ExtractableResponse response = RestAssured + .given() + .log().all() + .body(""" + { + "email": "test@koreatech.ac.kr", + "password": "1234" + } + """) + .contentType(ContentType.JSON) + .when() + .log().all() + .post("/user/login") + .then() + .log().all() + .statusCode(HttpStatus.CREATED.value()) + .extract(); + + RestAssured + .given() + .log().all() + .header("Authorization", "BEARER " + response.jsonPath().getString("token")) + .when() + .log().all() + .post("/user/logout") + .then() + .log().all() + .statusCode(HttpStatus.OK.value()) + .extract(); + + Optional token = tokenRepository.findById(user.getId()); + + Assertions.assertThat(token).isEmpty(); + } } From 64025660b03d173f038a96043c9dae1ad60e0007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Tue, 9 Jan 2024 15:22:48 +0900 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20=EC=9D=91=EB=8B=B5=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/dto/UserTokenRefreshRequest.java | 3 ++- .../koin/domain/user/service/UserService.java | 2 +- .../koreatech/koin/acceptance/AuthApiTest.java | 18 +++++++++++++++--- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/user/dto/UserTokenRefreshRequest.java b/src/main/java/in/koreatech/koin/domain/user/dto/UserTokenRefreshRequest.java index cbe02da56..c2955a01d 100644 --- a/src/main/java/in/koreatech/koin/domain/user/dto/UserTokenRefreshRequest.java +++ b/src/main/java/in/koreatech/koin/domain/user/dto/UserTokenRefreshRequest.java @@ -1,12 +1,13 @@ package in.koreatech.koin.domain.user.dto; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; import com.fasterxml.jackson.databind.annotation.JsonNaming; import jakarta.validation.constraints.NotNull; @JsonNaming(value = SnakeCaseStrategy.class) public record UserTokenRefreshRequest( - @NotNull(message = "refresh_token을 입력해주세요.") String refreshToken + @JsonProperty("refresh_token") @NotNull(message = "refresh_token을 입력해주세요.") String refreshToken ) { } diff --git a/src/main/java/in/koreatech/koin/domain/user/service/UserService.java b/src/main/java/in/koreatech/koin/domain/user/service/UserService.java index 22a5ed296..f86a148a7 100644 --- a/src/main/java/in/koreatech/koin/domain/user/service/UserService.java +++ b/src/main/java/in/koreatech/koin/domain/user/service/UserService.java @@ -52,7 +52,7 @@ public void logout(User user) { public UserTokenRefreshResponse refresh(User user, UserTokenRefreshRequest request) { UserToken userToken = userTokenRepository.findById(user.getId()) .orElseThrow(() -> new IllegalArgumentException("refresh token이 존재하지 않습니다. request: " + request)); - if (Objects.equals(userToken.getRefreshToken(), request.refreshToken())) { + if (!Objects.equals(userToken.getRefreshToken(), request.refreshToken())) { throw new IllegalArgumentException("refresh token이 일치하지 않습니다. request: " + request); } String accessToken = jwtProvider.createToken(user); diff --git a/src/test/java/in/koreatech/koin/acceptance/AuthApiTest.java b/src/test/java/in/koreatech/koin/acceptance/AuthApiTest.java index 4e46b3bca..652e35773 100644 --- a/src/test/java/in/koreatech/koin/acceptance/AuthApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/AuthApiTest.java @@ -12,6 +12,7 @@ import io.restassured.http.ContentType; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; +import java.util.Map; import java.util.Optional; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; @@ -165,16 +166,27 @@ void userRefreshToken() { .given() .log().all() .header("Authorization", "BEARER " + response.jsonPath().getString("token")) + .body( + Map.of("refresh_token", response.jsonPath().getString("refresh_token")) + ) + .contentType(ContentType.JSON) .when() .log().all() - .post("/user/logout") + .post("/user/refresh") .then() .log().all() .statusCode(HttpStatus.OK.value()) .extract(); - Optional token = tokenRepository.findById(user.getId()); + UserToken token = tokenRepository.findById(user.getId()).get(); - Assertions.assertThat(token).isEmpty(); + assertSoftly( + softly -> { + softly.assertThat(response.jsonPath().getString("token")).isNotNull(); + softly.assertThat(response.jsonPath().getString("refresh_token")).isNotNull(); + softly.assertThat(response.jsonPath().getString("refresh_token")) + .isEqualTo(token.getRefreshToken()); + } + ); } } From 52499bc0f5768f17a4d98986d7b21d391cecadbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Wed, 10 Jan 2024 19:35:03 +0900 Subject: [PATCH 4/5] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/user/controller/UserController.java | 3 +-- .../koin/domain/user/repository/UserTokenRepository.java | 2 ++ .../in/koreatech/koin/domain/user/service/UserService.java | 6 ++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/user/controller/UserController.java b/src/main/java/in/koreatech/koin/domain/user/controller/UserController.java index 7df7b7728..073efcd0d 100644 --- a/src/main/java/in/koreatech/koin/domain/user/controller/UserController.java +++ b/src/main/java/in/koreatech/koin/domain/user/controller/UserController.java @@ -36,10 +36,9 @@ public ResponseEntity logout(@UserAuth User user) { @PostMapping("/user/refresh") public ResponseEntity refresh( - @UserAuth User user, @RequestBody @Valid UserTokenRefreshRequest request ) { - UserTokenRefreshResponse tokenGroupResponse = userService.refresh(user, request); + UserTokenRefreshResponse tokenGroupResponse = userService.refresh(request); return ResponseEntity.ok().body(tokenGroupResponse); } } diff --git a/src/main/java/in/koreatech/koin/domain/user/repository/UserTokenRepository.java b/src/main/java/in/koreatech/koin/domain/user/repository/UserTokenRepository.java index 1af8fdd11..0a3d15984 100644 --- a/src/main/java/in/koreatech/koin/domain/user/repository/UserTokenRepository.java +++ b/src/main/java/in/koreatech/koin/domain/user/repository/UserTokenRepository.java @@ -10,5 +10,7 @@ public interface UserTokenRepository extends Repository { Optional findById(Long userId); + Optional findByRefreshToken(String refreshToken); + void deleteById(Long id); } diff --git a/src/main/java/in/koreatech/koin/domain/user/service/UserService.java b/src/main/java/in/koreatech/koin/domain/user/service/UserService.java index f86a148a7..be5dda27c 100644 --- a/src/main/java/in/koreatech/koin/domain/user/service/UserService.java +++ b/src/main/java/in/koreatech/koin/domain/user/service/UserService.java @@ -49,12 +49,14 @@ public void logout(User user) { userTokenRepository.deleteById(user.getId()); } - public UserTokenRefreshResponse refresh(User user, UserTokenRefreshRequest request) { - UserToken userToken = userTokenRepository.findById(user.getId()) + public UserTokenRefreshResponse refresh(UserTokenRefreshRequest request) { + UserToken userToken = userTokenRepository.findByRefreshToken(request.refreshToken()) .orElseThrow(() -> new IllegalArgumentException("refresh token이 존재하지 않습니다. request: " + request)); if (!Objects.equals(userToken.getRefreshToken(), request.refreshToken())) { throw new IllegalArgumentException("refresh token이 일치하지 않습니다. request: " + request); } + User user = userRepository.findById(userToken.getId()) + .orElseThrow(() -> new UserNotFoundException("사용자를 찾을 수 없습니다. refreshToken: " + userToken)); String accessToken = jwtProvider.createToken(user); return UserTokenRefreshResponse.of(accessToken, userToken.getRefreshToken()); } From f03034fd6b7458d1d363de2c402f000ae7a9937f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Thu, 11 Jan 2024 21:47:30 +0900 Subject: [PATCH 5/5] =?UTF-8?q?refactor:=20=EB=A6=AC=ED=94=84=EB=A0=88?= =?UTF-8?q?=EC=8B=9C=ED=86=A0=ED=81=B0=20userId=20=EA=B5=AC=EB=B6=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/user/service/UserService.java | 14 ++++++++++++-- .../in/koreatech/koin/acceptance/AuthApiTest.java | 1 - 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/user/service/UserService.java b/src/main/java/in/koreatech/koin/domain/user/service/UserService.java index be5dda27c..404114ff9 100644 --- a/src/main/java/in/koreatech/koin/domain/user/service/UserService.java +++ b/src/main/java/in/koreatech/koin/domain/user/service/UserService.java @@ -1,6 +1,7 @@ package in.koreatech.koin.domain.user.service; import in.koreatech.koin.domain.auth.JwtProvider; +import in.koreatech.koin.domain.auth.exception.AuthException; import in.koreatech.koin.domain.user.dto.UserLoginRequest; import in.koreatech.koin.domain.user.dto.UserLoginResponse; import in.koreatech.koin.domain.user.dto.UserTokenRefreshRequest; @@ -36,7 +37,7 @@ public UserLoginResponse login(UserLoginRequest request) { } String accessToken = jwtProvider.createToken(user); - String refreshToken = String.format("%s%d", UUID.randomUUID(), user.getId()); + String refreshToken = String.format("%s-%d", UUID.randomUUID(), user.getId()); UserToken savedToken = userTokenRepository.save(UserToken.create(user.getId(), refreshToken)); user.updateLastLoggedTime(LocalDateTime.now()); User saved = userRepository.save(user); @@ -50,7 +51,8 @@ public void logout(User user) { } public UserTokenRefreshResponse refresh(UserTokenRefreshRequest request) { - UserToken userToken = userTokenRepository.findByRefreshToken(request.refreshToken()) + String userId = getUserId(request.refreshToken()); + UserToken userToken = userTokenRepository.findById(Long.parseLong(userId)) .orElseThrow(() -> new IllegalArgumentException("refresh token이 존재하지 않습니다. request: " + request)); if (!Objects.equals(userToken.getRefreshToken(), request.refreshToken())) { throw new IllegalArgumentException("refresh token이 일치하지 않습니다. request: " + request); @@ -60,4 +62,12 @@ public UserTokenRefreshResponse refresh(UserTokenRefreshRequest request) { String accessToken = jwtProvider.createToken(user); return UserTokenRefreshResponse.of(accessToken, userToken.getRefreshToken()); } + + private static String getUserId(String refreshToken) { + String[] split = refreshToken.split("-"); + if (split.length == 0) { + throw new AuthException("올바르지 않은 인증 토큰입니다. refreshToken: " + refreshToken); + } + return split[split.length - 1]; + } } diff --git a/src/test/java/in/koreatech/koin/acceptance/AuthApiTest.java b/src/test/java/in/koreatech/koin/acceptance/AuthApiTest.java index 652e35773..b3a876800 100644 --- a/src/test/java/in/koreatech/koin/acceptance/AuthApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/AuthApiTest.java @@ -165,7 +165,6 @@ void userRefreshToken() { RestAssured .given() .log().all() - .header("Authorization", "BEARER " + response.jsonPath().getString("token")) .body( Map.of("refresh_token", response.jsonPath().getString("refresh_token")) )