김쥬르에 개발일지
Spring Boot JWT TestCode 본문
저번 포스팅에 이어서 JWT 테스트 코드를 통해
어떻게 동작하는지 살펴보고 구현이 잘되었는지 확인해보겠습니다.
test 디렉터리에 config.jwt 패키지를 만들고 JwtFactory.java 를 생성하고 코드를 작성합니다.
@Getter
public class JwtFactory {
private String subject = "test@email.com";
private Date issuedAt = new Date();
private Date expiration = new Date(new Date().getTime() + Duration.ofDays(14).toMillis());
private Map<String, Object> claims = emptyMap();
// 빌더 패턴을 사용해 설정이 필요한 데이터만 선택 설정
@Builder
public JwtFactory(String subject, Date issuedAt, Date expiration,
Map<String, Object> claims) {
this.subject = subject != null ? subject : this.subject;
this.issuedAt = issuedAt != null ? issuedAt : this.issuedAt;
this.expiration = expiration != null ? expiration : this.expiration;
this.claims = claims != null ? claims : this.claims;
}
public static JwtFactory withDefaultValues() {
return JwtFactory.builder().build();
}
// jjwt 라이브러리를 사용해 JWT 토큰 생성
public String createToken(JwtProperties jwtProperties) {
return Jwts.builder()
.setSubject(subject)
.setHeaderParam(Header.TYPE, Header.JWT_TYPE)
.setIssuer(jwtProperties.getIssuer())
.setIssuedAt(issuedAt)
.setExpiration(expiration)
.addClaims(claims)
.signWith(SignatureAlgorithm.HS256, jwtProperties.getSecretKey())
.compact();
}
}
빌더 패턴을 사용해 객체를 만들 때 테스트가 필요한 데이터만 선택합니다.
빌더 패턴을 사용하지 않으면 필드 기본값을 사용합니다.
같은 위치에 TokenProviderTest.java 파일을 만들고 코드를 작성합니다.
@SpringBootTest
class TokenProviderTest {
@Autowired
private TokenProvider tokenProvider;
@Autowired
private UserRepository userRepository;
@Autowired
private JwtProperties jwtProperties;
// generateToken() 검증 테스트
@DisplayName("generateToken(): 유저 정보와 만료 기간을 전달해 토큰을 만들 수 있다.")
@Test
void generateToken() {
// given
User testUser = userRepository.save(User.builder()
.email("user@gmail.com")
.password("test")
.build());
// when
String token = tokenProvider.generateToken(testUser, Duration.ofDays(14));
// then
Long userId = Jwts.parser()
.setSigningKey(jwtProperties.getSecretKey())
.parseClaimsJws(token)
.getBody()
.get("id", Long.class);
assertThat(userId).isEqualTo(testUser.getId());
}
// vaildToken() 검증 테스트
@DisplayName("validToken(): 만료된 토큰인 경우에 유효성 검증에 실패한다.")
@Test
void validToken_invalidToken() {
// given
String token = JwtFactory.builder()
.expiration(new Date(new Date().getTime() - Duration.ofDays(7).toMillis()))
.build()
.createToken(jwtProperties);
// when
boolean result = tokenProvider.validToken(token);
// then
assertThat(result).isFalse();
}
@DisplayName("validToken(): 유효한 토큰인 경우에 유효성 검증에 성공한다.")
@Test
void validToken_validToken() {
// given
String token = JwtFactory.withDefaultValues()
.createToken(jwtProperties);
// when
boolean result = tokenProvider.validToken(token);
// then
assertThat(result).isTrue();
}
// getAuthentication() 검증 테스트
@DisplayName("getAuthentication(): 토큰 기반으로 인증정보를 가져올 수 있다.")
@Test
void getAuthentication() {
// given
String userEmail = "user@email.com";
String token = JwtFactory.builder()
.subject(userEmail)
.build()
.createToken(jwtProperties);
// when
Authentication authentication = tokenProvider.getAuthentication(token);
// then
assertThat(((UserDetails) authentication.getPrincipal()).getUsername()).isEqualTo(userEmail);
}
// getUserId() 검증 테스트
@DisplayName("getUserId(): 토큰으로 유저 ID를 가져올 수 있다.")
@Test
void getUserId() {
// given
Long userId = 1L;
String token = JwtFactory.builder()
.claims(Map.of("id", userId))
.build()
.createToken(jwtProperties);
// when
Long userIdByToken = tokenProvider.getUserId(token);
// then
assertThat(userIdByToken).isEqualTo(userId);
}
}
generateToken() 메서드는 토큰을 생성하는 메서드를 테스트합니다.
| Given | 토큰에 유저 정보를 추가하기 위한 테스트 유저를 만듭니다. |
| When | 토큰 제공자의 generateToken() 메서드를 호출해 토큰을 만듭니다. |
| Then | jjwt 라이브러리를 사용해 토큰을 복호화합니다. 토큰을 만들때 클레임으로 넣어둔 id 값이 given 절에서 만든 유저 ID와 동일한지 확인합니다. |
validToken_invalidToken() 메서드는 토큰이 유효한 토큰인지 검증하는 메서드인 validToken() 메서드를 테스트합니다.
검증 실패를 확인하는 validToken_invalidToken() 메서드와
검증 성공을 확인하는 validToken_validToken() 메서드가 있습니다.
| Given | jjwt 라이브러리를 사용해 토큰을 생성합니다. 이때 만료 시간은 1970년 1월 1일부터 현재 시간을 밀리초 단위로 치환한 값(new Date().getTime())에 1000을 빼 , 이미 만료된 토큰으로 생성합니다. |
| When | 토큰 제공자의 validToken() 메서드를 호출해 유효한 토큰인지 검증한 뒤 결괏값을 반환받습니다. |
| Then | 반환값이 false(유효한 토큰이 아님)인 것을 확인합니다. |
| Given | jjwt 라이브러리를 사용해 토큰을 생성합니다. 만료 시간은 현재 시간으로부터 14일 뒤로 만료되지 않은 토큰으로 생성합니다. |
| When | 토큰 제공자의 validToken() 메서드를 호출해 유효한 토큰인지 검증한 뒤 결괏값을 반환받습니다. |
| Then | 반환값이 ture(유효한 토큰임)인 것을 확인합니다. |
getAuthentication() 메서드는 토큰을 전달받아 인증 정보를 담은 객체
Authentication를 반환하는 메서드인 getAuthentication()를 테스트합니다.
| Given | jjwt 라이브러리를 사용해 토큰을 생성합니다. 이때 토큰의 제목인 subject는 "user@email.com" 라는 값을 사용합니다. |
| When | 토큰 제공자의 getAuthentication() 메서드를 호출해 인증 객체를 반환받습니다. |
| Then | 반환받은 인증 객체의 유저 이름을 가져와 given절에서 설정한 subject값인 "user@email.com"과 같은지 확인합니다. |
getUserId() 메서드는 토큰 기반으로 유저 ID를 가져오는 메서드를 테스트하는 메서드입니다.
토큰을 프로퍼티즈 파일에 저장한 비밀값으로 복호화한 뒤 클레임을 가져오는
private 메서드인 getClaims()를 호출해서 클레임 정보를 반환받아 클레임에서
id키로 저장된 값을 가져와 반환합니다.
| Given | jjwt 라이브러리를 사용해 토큰을 생성합니다. 이때 클레임을 추가합니다. 키는 "id" 값은 1이라는 유저 ID입니다. |
| When | 토큰 제공자의 getUserId() 메서드를 호출해 유저 ID를 반환받습니다. |
| Then | 반환받은 유저 ID가 given절에서 설정한 유저 ID값인 1과 같은지 확인합니다. |
이제 테스트 코드를 실행하여 잘 동작하는지 확인합니다.

이번엔 리프레시 토큰에 대한 TestCode를 작성해보겠습니다.
test/.../controller 패키지에 TokenApiControllerTest.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의 요청 본문에 리프레시 토큰을 포함하여 요청 객체를 생성합니다. |
| When | 토큰 추가 API에 요청을 보낸다 이때 요청 타입은 JSON이며 given절에서 미리 만들어둔 객체를 요청 본문으로 함께 보냅니다. |
| Then | 응답 코드가 201 Created인지 확인하고 응답으로 온 엑세스 토큰이 비어있지 않은지 확인합니다. |
테스트 코드를 실행하여 실제로 잘 동작하는지 확인합니다.

다음 포스팅엔 Oauth2를 사용하여 소셜 로그인을 구현해보겠습니다.
'Spring boot' 카테고리의 다른 글
| Spring Boot AWS 배포 (0) | 2024.01.11 |
|---|---|
| Spring Boot OAuth2 (0) | 2024.01.08 |
| Spring Boot JWT (1) | 2024.01.05 |
| Spring Boot 스프링 시큐리티 (0) | 2024.01.01 |
| Spring Boot 각종 뷰 (1) | 2023.12.31 |