반응형

1. 과제

 JWT 토큰을 사용하여 인증, 인가를 구현하라

 

2. 배운점

2.1. JWT

 JWT 토큰을 적용하여 인증 처리를 하였다. 토큰 적용 후 사용목적이나 한계점, 효과적인 토큰 사용 방법들에 대해 공부하며 JWT 토큰에 대한 개념을 확립할 수 있었다. 정리한 개념은 따로 포스팅하였다.

https://tlatmsrud.tistory.com/87

 

2.2. Interceptor에 대한 테스트 코드

 요청에 대한 권한 여부를 체크하는 방식으로 커스텀 어노테이션 방식을 채택했다.

클라이언트로부터 요청이 들어왔을 때 실행되는 컨트롤러 메서드에 커스텀 어노테이션(@CheckJwtToken)이 있을 경우 JWT 토큰을 검증하도록 하였으며 이를 위해 앞단에 Interceptor를 구현하였다.

 Interceptor의 테스트 코드 작성 시 preHandler메서드에 대한 테스트 작성이 어려웠다. HttpServletRequest, HttpServletResponse, Handler에 대한 파라미터를 테스트코드에서 어떻게 넘겨야할지 감이 안잡혔기 때문이다. 우여곡절 끝에 MockHttpServletRequest, MockHttpServletResponse, HandlerMethod로 구현하게 되었다.(멘토님 감사합니다.)

HandlerMethod로 구현한 이유
 디스패처 서블릿은 애플리케이션이 실행될 때 모든 컨트롤러의 메소드를 추출한 뒤 HandlerMethod 형태로 저장해두고, 실제 요청이 들어오면 요청 조건에 맞는 HandlerMethod를 참조하여 해당 메서드를 실행시킨다.
 실제 API에 대한 요청이 들어올 경우 Interceptor의 preHandler 메서드 파라미터인 handler에 HandlerMethod가 들어오기 때문에 HandlerMethod로 구현하였다.

 

HandlerMethod 생성자 파라미터는 beanObject, methodName, parameter로 구성되는데, 테스트 클래스에 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. ParameterizedTest

 테스트 메서드의 로직은 동일하나 특정 변수 값만 다른 테스트 케이스가 있다. 예를들어 유효하지 않은 토큰 검증에 대한 테스트 시 토큰의 요청 값으로 null, 빈 값, 쓰레기값이 들어갈 수 있는데, 이를 각각 테스트하기 위해 다음과 같이 3개 메서드로 구현할 수 있다.

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 토큰에 대한 기본 개념, 사용 이유, 보안상 이점, 한계점과 같은 것들을 공부하게 되어 좋았다. 

 테스트 코드는 Controller, Service, Repository 정도로만 구현을 하다가 이번에 처음으로 Interceptor를 구현하게 되었는데 처음 해보는거라 감이 아예 잡히지 않았었다. prehandle 메서드 검증을 위해 파라미터를 채우는 방법을 몰랐기 때문이었는데, 멘토님께서 도와주시어 해결할 수 있었다.

 이제 2주 남았다. 남은 2주도 미루지 않고 잘 해보자!

반응형
반응형

1. 과제

 객체지향적인 REST API를 구현하라

 

2. 배운점

2.1. '객체에게 묻기보단 시켜야 한다.'

 이번 주차에 가장 나에게 와닿았던 피드백이다. 객체가 갖고있는 정보에 대해서 해야할 일이 있을 때 단순히 그 정보를 받아온 후에 다른 객체에서 뭔가를 처리하는게 아니라 그 처리마저도 객체에게 시켜야한다는 의미였다.

처음엔 이 의미가 이해가 가지 않아 현재 내 코드를 '후임에게 업무를 맡기는 상황'에 적용해보았다.

 

 일반적으로 후임에게 업무를 맡길때  '~에 대한 A, B, C 업무를 모두 처리해주시고, 저한테 보고해주세요' 라고 시킨다. 그런데 내 코드는 '~에 대한 업무를 처리해주시되, C 업무는 저한테 주시면 제가 알아서 처리할게요' 였다. (ㅜㅠ)

객체의 멤버필드에 대한 비지니스 로직이 필요한 부분이 있었는데, 나는 해당 멤버필드를 get메서드로 가져온 후 다른 객체에서 로직을 처리하고 있었기 때문이다.

 

 만약 이 상황에서 업무 내용이 변경되거나 차질이 생긴다면 어떨까?

 전자의 경우 업무에 대한 모든 책임을 후임이 맡게된다. 후임은 작업을 하고 나는 보고받으면 된다.

 후자의 경우 A, B 업무에 대한 책임은 후임이, C 업무에 대한 책임은 내가 맡게 된다. 맡은 업무에 대해서 작업은 책임을 맡은 사람이 하며, A, B, C 모두 관련성 있는 업무이기 때문에 나와 후임 모두 사이드 이팩트를 확인하는 일을 추가적으로 해야한다. 결국 업무에 대한 응집도는 분산되어 낮아지고, 결합도는 나와 후임으로 증가하는 느낌이 들었다.

 

 '객체에게 묻기보단 시켜야 한다.' 라는 피드백이 객체지향적인 설계의 가장 기본을 지키세요 라는 의미가 아니었을까 라는 생각과 함께 자바 개발자로써의 부끄러움와 지금이라도 알게되어 다행이라는 안도감이 몰려왔다.

 

2.2. 관습을 지키자.

 코드를 짜면서 사실 언어의 관습에 대해 생각해본적이 없다. 언어별 코드 정렬 방식, 변수 네이밍 및 케이스 등.

그런데 관습을 지키지 않는다면 관습을 지키는 개발자들에게 혼란을 야기할 수 있다는 걸 알았다.

 

2.3. 변수명에 대한 고민

 변수명은 개발자가 이해하기 쉬운 이름으로 설정하자. 불필요한 축약이나 본인만 알 수 있는 네이밍을 피해야 하고, 상황에 따라 실제 사용하는 기술 명칭을 넣는 것도 방법이다.

 

배운점을 3개정도 썼지만 사실 이것말고도 아주 많다. 1번은 너무 강렬하게 다가와 꼭 정리를 하고 싶어 길게 썼다.

 

3. 느낀점

 처음 과제를 접했을 때 너무 간단하게 느껴져 하루 이틀이면 끝날것이라 생각했다.

 하지만 코드를 fork, commit, pr하는 과정부터 버벅거렸고, 코드 리뷰를 받다보니 어느새 일주일이 지나있었다. 멘토님께서 개선이 필요한 부분을 찾아주셨으나, 계속적으로 피드백이 온걸 보면 거의 지뢰찾기 게임을 하시지 않았나 싶다. 너무 감사했지만 한편으로는 조금 죄송스러웠다.

 이제 1주차인데 나의 잘못된 코딩 습관들을 고쳐야 하고, 언어에 대해 배워야할 것들이 아주 많다는 것을 느꼈다. 언어의 특성에 대해 너무 생각없이 개발해 온 지난 세월에 대해 안타까움도 느껴졌다. 이번 피드백을 머릿속에 새기고 내일부터 있을 2주차에 적용할 수 있도록 고민하며 개발할 것이다. 2주차도 열심히!

 

 

 

 

반응형

+ Recent posts