(코드 숨, Spring 15) 6주차 요약

1차 주문

JWT 토큰을 사용하여 인증 및 권한 부여 구현

2. 배운 교훈

2.1. JWT

JWT 토큰을 적용하여 인증을 수행했습니다.

토큰을 적용한 후 JWT 토큰의 목적, 한계 및 효과적인 토큰 사용 방법을 연구하여 JWT 토큰의 개념을 정립할 수 있었습니다.

2.2. 인터셉터 테스트 코드

요청이 승인되었는지 확인하기 위해 사용자 정의 주석 방법이 도입되었습니다.

클라이언트에서 요청이 들어올 때 실행되는 컨트롤러 메서드에 사용자 지정 주석(@CheckJwtToken)이 있는 경우 JWT 토큰이 확인되고 해당 목적을 위해 인터셉터가 미리 구현됩니다.

Interceptor 테스트 코드를 작성할 때 preHandler 메소드에 대한 테스트 작성이 어려웠습니다.

테스트 코드에서 HttpServletRequest, HttpServletResponse 및 Handler 매개변수를 전달하는 방법을 몰랐기 때문입니다.

우여곡절 끝에 MockHttpServletRequest, MockHttpServletResponse 및 HandlerMethod를 사용하여 구현되었습니다(멘토 덕분에).

HandlerMethod로 구현하는 이유는 무엇입니까?
애플리케이션이 실행되면 Dispatcher servlet은 모든 컨트롤러 메소드를 추출하여 HandlerMethod 형태로 저장하고 실제 요청이 들어오면 요청 조건을 만족하는 HandlerMethod를 참조하여 메소드를 실행한다.


실제 API 요청이 들어오면 Interceptor의 preHandler 메소드의 파라미터인 HandlerMethod가 들어오기 때문에 HandlerMethod가 HandlerMethod로 구현된다.

생성자 파라미터인 HandlerMethod는 beanObject, methodName, 파라미터로 구성되어 있으며 테스트 클래스에 MockHandler 클래스를 생성하여 다음과 같이 구현하고 beanObject methodName의 테스트 클래스 @CheckJwtToken을 포함한 메서드 이름이 기술됩니다.

이를 통해 Interceptor의 테스트 코드를 작성할 수 있었습니다.

다음은 MockHandler 클래스 및 PreHandler 메서드에 대한 테스트 코드입니다.

class MockHandler{
	@CheckJwtToken
    public void handleRequest(){

    }
}

@Test
	void withInvalidTokenReturnFalse() throws Exception {
		MockHttpServletRequest request = new MockHttpServletRequest();
 		MockHttpServletResponse response = new MockHttpServletResponse();

		HandlerMethod handler = new HandlerMethod(new MockHandler(), "handleRequest");

		request.setMethod("GET");
		request.addHeader("Authorization", "Bearer "+INVALID_TOKEN);

		// 유효하지 않은 토큰일 경우 PreHandle 메서드에서 예외가 발생하며 최종적으로 false를 리턴함.
		boolean result = loginCheckInterceptor.preHandle(request, response, handler);

		assertThat(result).isFalse();
 }

2.3. 매개변수화된 테스트

테스트 메소드의 논리는 같지만 특정 변수의 값만 다른 테스트 케이스가 있습니다.

예를 들어 유효하지 않은 토큰 검증을 테스트할 때 토큰 요청 값으로 null, empty 또는 garbage 값을 입력할 수 있습니다.

void parseTokenWithBlankToken(){
	assertThatThrownBy(() -> authenticationService.parseToken(" "))
  		.isInstanceOf(InvalidTokenException.class);
}

void parseTokenWithNullToken(){
	assertThatThrownBy(() -> authenticationService.parseToken(null))
  		.isInstanceOf(InvalidTokenException.class);
}

void parseTokenWithInvalidToken(){
    // INVALID_TOKEN 은 실제 유효하지 않은 토큰을 담은 static 변수임.
	assertThatThrownBy(() -> authenticationService.parseToken(INVALID_TOKEN))
  		.isInstanceOf(InvalidTokenException.class);
}

위의 경우 테스트 로직은 같지만 토큰 값이 다르기 때문에 각각의 메서드를 생성하여 처리했습니다.

특정 변수의 값만 다르고 로직이 같다면 ParameterizedTest를 통해 메소드로 테스트가 가능하다.

@ParameterizedTest
    @ValueSource(strings = { INVALID_TOKEN, "", null})
    void parseTokenWithInvalidTokens(String token){
        assertThatThrownBy(() -> authenticationService.parseToken(token))
                .isInstanceOf(InvalidTokenException.class);
    }

3. 느낀 점

실제로 JWT 토큰의 적용을 확인했습니다.

. 응용 목적에 대한 내용이라 개념적인 내용을 많이 생략한 것 같았는데 이번 기회에 JWT 토큰의 기본 개념과 사용 이유, 보안상의 장점과 한계에 대해 공부할 수 있어서 좋았습니다.

테스트 코드는 컨트롤러, 서비스, 리포지토리 수준에서만 구현했는데 이번에 처음으로 Interceptor를 구현했습니다.

프리핸들 메서드 유효성 검사를 위한 파라미터를 어떻게 채워야 할지 몰라서 그랬는데 멘토님의 도움으로 해결할 수 있었습니다.

이제 2주 더. 남은 2주도 망설이지 말고 잘해보자!