Notice
Recent Posts
Recent Comments
Link
«   2026/05   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
Tags
more
Archives
Today
Total
관리 메뉴

김쥬르에 개발일지

Spring Boot JWT TestCode 본문

Spring boot

Spring Boot JWT TestCode

김쥬르 2024. 1. 6. 22:56

저번 포스팅에 이어서 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