Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -31,4 +33,12 @@ public ResponseEntity<Void> logout(@UserAuth User user) {
userService.logout(user);
return ResponseEntity.ok().build();
}

@PostMapping("/user/refresh")
public ResponseEntity<UserTokenRefreshResponse> refresh(
@RequestBody @Valid UserTokenRefreshRequest request
) {
UserTokenRefreshResponse tokenGroupResponse = userService.refresh(request);
return ResponseEntity.ok().body(tokenGroupResponse);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +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(
@JsonProperty("refresh_token") @NotNull(message = "refresh_token을 입력해주세요.") String refreshToken
) {

}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ public interface UserTokenRepository extends Repository<UserToken, Long> {

Optional<UserToken> findById(Long userId);

Optional<UserToken> findByRefreshToken(String refreshToken);

void deleteById(Long id);
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
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;
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;
Expand All @@ -33,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);
Expand All @@ -45,4 +49,25 @@ public UserLoginResponse login(UserLoginRequest request) {
public void logout(User user) {
userTokenRepository.deleteById(user.getId());
}

public UserTokenRefreshResponse refresh(UserTokenRefreshRequest request) {
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);
}
User user = userRepository.findById(userToken.getId())
.orElseThrow(() -> new UserNotFoundException("사용자를 찾을 수 없습니다. refreshToken: " + userToken));
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];
}
}
62 changes: 62 additions & 0 deletions src/test/java/in/koreatech/koin/acceptance/AuthApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -126,4 +127,65 @@ 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> 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()
.body(
Map.of("refresh_token", response.jsonPath().getString("refresh_token"))
)
.contentType(ContentType.JSON)
.when()
.log().all()
.post("/user/refresh")
.then()
.log().all()
.statusCode(HttpStatus.OK.value())
.extract();

UserToken token = tokenRepository.findById(user.getId()).get();

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());
}
);
}
}