리프레시 토큰을 전달 받아 토큰 제공자를 사용해 새로운 엑세스 토큰을 만드는 토큰 서비스 클래스를 생성한다.
전달받은 유저 ID로 유저를 검색해서 전달하는 findById() 메서드 추가
// service - UserService.java
@RequiredArgsConstructor
@Service
public class UserService {
~ 생략 ~
public User findById(Long userId) {
return userRepository.findById(userId)
.orElseThrow(()->new IllegalArgumentException("Unexpected user"));
}
~ 생략 ~
}
전달 받은 리프레시 토큰으로 리프레시 토큰 객체를 검색해서 전달하는 findByRefreshToken() 메서드 추가
// service - RefreshTokenService.java
@RequiredArgsConstructor
@Service
public class RefreshTokenService {
private final RefreshTokenRepository refreshTokenRepository;
public RefreshToken findByRefreshToken(String refreshToken) {
return refreshTokenRepository.findByRefreshToken(refreshToken)
.orElseThrow(() -> new IllegalArgumentException("Unexpected token"));
}
}
전달 받은 리프레시 토큰으로 토큰 유효성 검사를 진행하고, 유효한 토큰일 때 리프레시 토큰으로 사용자 ID를 찾은 후 해당 ID로 사용자를 찾은 후 TokenProvider의 generateToken()메서드를 호출해서 새로운 엑세스 토큰을 생성한다.
// service - TokenService.java
@RequiredArgsConstructor
@Service
public class TokenService {
private final TokenProvider tokenProvider;
private final RefreshTokenService refreshTokenService;
private final UserService userService;
public String createNewAccessToken(String refreshToken) {
// 토큰 유효성 검사에 실패하면 예외 발생
if(!tokenProvider.validToken(refreshToken)) {
throw new IllegalArgumentException("Unexpected token");
}
Long userId = refreshTokenService.findByRefreshToken(refreshToken).getUserId();
User user = userService.findById(userId);
return tokenProvider.generateToken(user, Duration.ofHours(2));
}
}
토큰 생성 요청 및 응답을 담당할 DTO 클래스들을 작성한다.
// dto - CreateAccessTokenRequest.java
@Getter
@Setter
public class CreateAccessTokenRequest {
private String refreshToken;
}
// dto - CreateAccessTokenResponse.java
@AllArgsConstructor
@Getter
public class CreateAccessTokenResponse {
private String accessToken;
}
/api/token POST 요청이 오면 토큰 서비스에서 리프레시 토큰을 기반으로 새로운 엑세스 토큰을 만들어주는 컨트롤러를 생성한다.
// controller - TokenApiController.java
@RequiredArgsConstructor
@RestController
public class TokenApiController {
private final TokenService tokenService;
@PostMapping("/api/token")
public ResponseEntity<CreateAccessTokenResponse> createNewAccessToken(@RequestBody CreateAccessTokenRequest request) {
String newAccessToken = tokenService.createNewAccessToken(request.getRefreshToken());
return ResponseEntity.status(HttpStatus.CREATED)
.body(new CreateAccessTokenResponse(newAccessToken));
}
}
.body(new CreateAccessTokenResponse(newAccessToken))
ResponseEntity의 본문(body)을 설정한다. 이 경우 CreateAccessTokenResponse 객체를 생성하여 이 객체를 본문으로 설정합니다. 생성된 access token을 CreateAccessTokenResponse 객체에 포함시킨다. 이렇게 함으로써 클라이언트가 새로운 access token을 수신할 수 있게 된다.
// test - controller - TokenApiController.java
@SpringBootTest
@AutoConfigureMockMvc
class TokenApiControllerTest {
@Autowired
protected MockMvc mockMvc;
@Autowired
protected ObjectMapper objectMapper;
@Autowired
private WebApplicationContext context;
@Autowired
JwtProperties jwtProperties;
@Autowired
UserRepository userRepository;
@Autowired
RefreshTokenRepository refreshTokenRepository;
@BeforeEach
public void mockMvcSetUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
.build();
userRepository.deleteAll();
}
@DisplayName("createNewAccessToken: 새로운 액세스 토큰을 발급한다.")
@Test
public void createNewAccessToken() throws Exception {
// given
final String url = "/api/token";
User testUser = userRepository.save(User.builder()
.email("user@gmail.com")
.password("test")
.build());
String refreshToekn = JwtFactory.builder()
.claims(Map.of("id", testUser.getId()))
.build()
.createToken(jwtProperties);
refreshTokenRepository.save(new RefreshToken(testUser.getId(), refreshToekn));
CreateAccessTokenRequest request = new CreateAccessTokenRequest();
request.setRefreshToken(refreshToekn);
final String requestBody = objectMapper.writeValueAsString(request);
// when
ResultActions resultActions = mockMvc.perform(post(url)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(requestBody));
// then
resultActions
.andExpect(status().isCreated())
.andExpect(jsonPath("$.accessToken").isNotEmpty());
}
}
Given
테스트 유저를 생성하고, jjwt 라이브러리를 이요해 리프레시 토큰을 만들어 데이터베이스에 저장한다.
토큰 생성 API의 요청 본문에 리프레시 토큰을 포함하여 요청 객체를 생성한다.
ObjectMapper
Jackson 라이브러리에서 제공하는 클래스로, Java 객체와 JSON 데이터 간의 변환을 담당한다.
writeValueAsString()
ObjectMapper의 메서드 중 하나로, 주어진 객체를 JSON 문자열로 변환한다. 이 메서드는 CreateAccessTokenRequest 객체(request)를 JSON 형식의 문자열로 변환하여 반환한다. 이렇게 변환된 JSON 문자열은 HTTP 요청의 본문(body)에 포함되어 서버에 전송된다.
When
토큰 추가 API에 요청을 보낸다.이때 요청 타입은 JSON이며, given절에서 미리 만들어둔 객체를 요청 본문으로 함께 보낸다.
Then
응답 코드가 201 Created인지 확인하고 응답으로 온 엑세스 토큰이 비어있지 않은지 확인한다.