
package com.it.blog.service;
import com.it.blog.config.jwt.TokenProvider;
import com.it.blog.domain.User;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.time.Duration;
@Service
@RequiredArgsConstructor
public class TokenService {
//createNewAccessToken
//전달 받은 리프레시 토큰으로 토큰 유효성 검사하고
//유효한 토큰 일 때 리프레시 토큰으로 사용자 ID를 찾는다.
//그 이후에 generateToken 메소드를 이용해 새로운 엑세스 토큰을 생성
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));
}
}
package com.it.blog.service;
import com.it.blog.domain.RefreshToken;
import com.it.blog.repository.RefreshTokenRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class RefreshTokenService {
private final RefreshTokenRepository refreshTokenRepository;
public RefreshToken findByRefreshToken(String refreshToken){
return refreshTokenRepository.findByRefreshToken(refreshToken)
.orElseThrow(()-> new IllegalArgumentException("Unexpected token"));
}
}
package com.it.blog.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class CreateAccessTokenResponse {
//토큰 생성 요청 응답DTO
private String accessToken;
}
package com.it.blog.controller;
import com.it.blog.dto.CreateAccessTokenRequest;
import com.it.blog.dto.CreateAccessTokenResponse;
import com.it.blog.service.TokenService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@Controller
@RequiredArgsConstructor
public class TokenApiController {
// /api/token 요청이 들어오면
// 토큰 서비스에서 리프레시토큰을 기반으로
// 새로운 엑세스 토큰을 만들어준다.
private final TokenService tokenService;
@PostMapping
public ResponseEntity<CreateAccessTokenResponse> createNewAccessToken(@RequestBody CreateAccessTokenRequest request){
String newAccessToken = tokenService.createNewAccessToken(request.getRefreshToken());
return ResponseEntity.status(HttpStatus.CREATED)
.body(new CreateAccessTokenResponse(newAccessToken));
}
}
package com.it.blog.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.it.blog.config.jwt.JwtFactory;
import com.it.blog.config.jwt.JwtProperties;
import com.it.blog.domain.RefreshToken;
import com.it.blog.domain.User;
import com.it.blog.dto.CreateAccessTokenRequest;
import com.it.blog.repository.BlogRepository;
import com.it.blog.repository.RefreshTokenRepository;
import com.it.blog.repository.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
public 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("새로운 액세스 토큰 발급")
@Test
public void createNewAccessToken() throws Exception{
//given
/*
* 테스트 유지 생성하고 jjwt 라이브러리를 활용해
* 리프레시 토큰을 만들어서 DB에 저장
* 토큰 생성API의 요청 본문에 리프레시 토큰을 포함해
* 요청 객체 생성
* */
final String url = "/api/token";
User testUser = userRepository.save(
User.builder()
.email("user@email.com")
.password("test")
.build()
);
String refreshToken = JwtFactory.builder()
.claims(Map.of("id", testUser.getId()))
.build().createToken(jwtProperties);
refreshTokenRepository.save(new RefreshToken(testUser.getId(), refreshToken));
CreateAccessTokenRequest request = new CreateAccessTokenRequest();
request.setRefreshToken(refreshToken);
//요청 객체
final String requestBody = objectMapper.writeValueAsString(request);
//when
/*
* 토큰 추가 API 요청 보내기
* 요청 타입은 JSON*/
ResultActions resultActions = mockMvc.perform(post(url)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(requestBody));
//then
/*
* 응답코드가 201created 인지 확인
* 액세스 토큰이 비어있지 않은지 확인*/
resultActions.andExpect(status().isCreated())
.andExpect(jsonPath("$.accessToken").isNotEmpty());
}
}
오탈자를 항상 주의하자 :)
