[SpringBoot]@ResquestHeader 에서 특정 문자 값만 매핑하는 Annotation 만들기(ArgumentResolver 구조)

문상우·2023년 4월 21일
0

SpringBoot

목록 보기
2/2
post-thumbnail

문제제기

내가 현재 하고 있는 프로젝트는 Spring Security 와 Jwt 토큰을 사용하여 해당 권한에 대해 인증 및 인가를 처리하고 있다. 따라서, AccessToken을 Header에서 받아와야 하는 일이 많아졌고,, 이를 단순히 @RequestHeader("accessToken") 를 통해 하다보니 한 글자라도 오타가 나면 오류가 생기는 문제에 봉착했다.. 따라서 아예 accessToken 만 전문적으로 처리할 수 있는 annotation을 만들어 보기로 한다.

Motive

위에서 얘기했듯이 현재 진행하고 있는 프로젝트는 Jwt 와 Spring Security 를 통한 인증 및 인가로 accessToken 이 꼭 필요하다. 또한, 인증 인가 뿐만 아니라 accessToken 에 들어있는 정보를 사용하기 위해서는 Header에 있는 accessToken 을 받아와야만 한다. 때문에 controller 에서 이를 받아오기 위한 애노테이션이 필요했다.

  1. HttpServletRequest
    Spring 에서 제공하는 Servlet 을 간단하게 얘기하자면 Http 통신에서 Request 메시지를 꺼내고 Response 메시지를 만들어 주는 역할을 한다. 그렇기에 이를 이용하면 우리는 요청 응답에 대해 신경 쓰지 않아도, 비지니스 로직만 작성하면 이외의 것들은 Servlet 이 처리해준다. HttpServletRequest 는 Spring 이 제공하는 기능으로 Servlet 을 통해 요청 정보를 단순히 getter 메서드로 꺼내서 사용할 수 있게 해준다.

    위처럼 단순히 파라미터 값으로 HttpServletRequest 를 받아오는 것 만으로 헤더에 있는 accessToken 을 꺼내 쓸 수 있다. 하지만 controller 레벨에서 이 모든 요청 정보를 받아올 필요가 있을까.. ?

  2. @RequestHeader
    이는 위에서 사용한 HttpServletRequest.getHeader() 의 기능을 똑같이 사용할 수 있도록 하는 애노테이션이다. 이를 사용하면 위 코드보다 훨씬 간결하게 토큰을 받아올 수 있다.

    정상적으로 출력되는것을 확인할 수 있다. 하지만, 위 코드의 문제는 RequestHeader 의 파라미터에 문자 즉, String 값을 넣어 Header 의 정보와 매칭시킨다는 것이다. 따라서 위 코드에서 "accessToken" 에 한글자라도 오류가 난다면 위 코드는 정상적으로 실행될 수 없을 것이다.

    이처럼 Header에 있는 accessToken 을 불러오는 일이 한 두번만 있다면 이를 신경쓰지 않아도 되겠지만, 내가 하고 있는 프로젝트처럼 이런 일이 빈번히 일어난다면 이는 문제가 일으킬 가능성이 크다.

    따라서 나는 Controller 에서 모든 헤더의 요청 정보 알지 않아도 되고, 문자 리터럴 값의 매칭 오류를 막기 위해 새로운 애노테이션을 만들기로 했다.


시작

간단할 것이다. 단지 하나의 애노테이션을 만들어 이 애노테이션에 추가로 @RequestHeader 를 넣으면 될 것이다. 라는 생각.. 아주 오만하다.

시작하자마자 발생한 오류.. @Service, @Repository 안에 @Component 가 있어, @Component 를 추가로 적어주지 않아도 알아서 자동 의존주입이 된다는 사실을 알았던 나는 단지 내가 만들고자하는 @RequestAccessToken 위에 @RequestHeader 만 적어주면 될거라고 생각했다.
하지만, "메서드 매개 변수가 웹 요청 헤더에 바인딩되어야 함을 나타내는 주석입니다." 라는 메세지와 함께 오류가 발생했다. 잘 생각해보면, Controller 에서 사용되는 애노테이션이고 요청할 때 들어오는 param 값을 매핑해서 가져오는 이 애노테이션의 특성상 말이 안되는 생각이였다.

ArgumentResolver

ArgumentResolver
해당 문제의 해결점은 이곳에 있다. Spring MVC 패턴을 알고 있는 사람이라면 모를 수가 없는 ArgumentResolver 이다.


Spring MVC Pattern

출처:https://inf.run/8wZ6

이 사진은 김영한 선생님이 만드신 Spring MVC 패턴이다. 이 그림에서는 controller에 오기 전에 HandlerAdapter 를 거친다는 사실만 알면 된다. 우리에게 필요한 ArgumentHandler 와 관련된 처리는 4번 동작에서 실행된다(4번에서 얘기하는 Handler 는 Controller 를 뜻함).


ArgumentResolver

출처:https://inf.run/8wZ6

이는 김영한 선생님이 만드신 RequestMappingHandlerAdapter 동작 방식이다. 사실 이 글에서 RequestMappingHandlerAdapter 동작 방식 중 알아야 할 사실은 우리가 지금껏 편하게 사용했던 HttpServletRequest, @RequestBody, @RequestParam, @RequestHeader 등등은 결국 ArgumentResolver 덕분이라는 것이다.

동작방식

  1. 핸들러 어댑터는 위 ArgumentResolver 들이 모여 있는 리스트를 돌면서 컨트롤러에서 요청한 파라미터를 처리할 수 있는 ArgumentResolver를 찾는다.
  2. 찾은 ArgumentResolver 는 MessageConverter 를 통해 파라미터 객체로 파싱하여 컨트롤러에 전달한다.
  3. 반환은 ArgumentResolver 가 아닌 ReturnValueHandler 를 통해 위 과정과 비슷한 과정을 통해 Response 값을 만들어 RequestMappingHandlerAdapter 에게 전달한다.

우리는 위 3가지 과정을 통해 지금껏 간단한 명령어로 파라미터를 객체로 만들어 사용할 수 있었다. 이 과정을 이해함으로 내가 원했던 결과에 다가갈 수 있다.

문제 해결

위에서 얘기한 내용을 쉽게 요약해보자.

  1. controller 에서 요청한 파라미터 값을 찾기 위해 ArgumentResolver를 호출한다.
  2. 여러 ArgumentResolver 중 controller 에서 요청한 파라미터 값을 해결해줄 수 있는 ArgumentResolver를 찾고, 그 곳으로 요청 정보를 전달한다.
  3. 해당 ArgumentResolver 는 controller 에게 요청 정보에 맞는 파라미터 정보를 가공한 후 반환한다.

위 세 가지의 방식을 이해하고 문제를 해결한다.

  1. 애노테이션을 만든다.

  2. 위 애노테이션을 처리할 수 있는 ArgumentResolver 를 만든다.

  1. HandlerAdapter가 호출하는 ArgumentResolver 리스트에 우리가 만든 ArgumentResolver 를 추가한다.

설명

각 문제 해결에 대한 설명이 없다면 정말 간단해 보인다. 매번 이런 식이다. 실제로는 간단한데 엄청 거대한 벽처럼 느껴지는..

  1. 애노테이션을 만든다. interface로 생성 후, 앞에 @를 붙이면 애노테이션으로 인식한다.

    • @Target : 어노테이션 적용할 위치 선택
    • @Retention : 컴파일러가 어노테이션을 다루는 방법을 기술, 어느 시점까지 영향을 미치는지를 결정
    • @Documented : 해당 어노테이션을 Javadoc에 포함시킴
  2. HandlerMethodArgumentResolver 를 구현한다.
    스프링에서는 우리가 직접 애노테이션을 처리하는 ArgumentResolver 만들 수 있도록 HandlerMethodArgumentResolver를 인터페이스로 구현해놓았다(확정성 굳).

    즉, 우리는 이 인터페이스에 있는 supportsParameter 와 resolveArgument 이 두개의 메서드만 구현하면 ArgumentResolver 리스트에 이를 추가시켜 애노테이션을 커스텀하거나 만들 수 있다.

    • supportsParameter : 이 메서드의 값이 true 면 resolveArgument가 동작한다. 여러 ArgumentResolver 들을 순환하면서 어떤 ArgumentResolver 가 채택될지는 이 메서드에 달려있다.

    • resolveArgument : 이 메서드의 반환값은 실제 애노테이션이 동작하는 파라미터 값에 들어간다.

  • supportsParameter 에서 우리가 만든 애노테이션을 요청했는지의 조건문을 넣고, resolveArgument에서 Spring이 제공하는 NativeWebRequest 의 getParameter() 메서드를 이용하여 accessToken 값을 찾아 반환시키면 우리가 그토록 원했던 결과를 도출해낼 수 있다.
  1. 사실은 아직이다..
    우리는 모든 기능을 만들었지만, 결국 실패한다 왜 ? HandlerAdapter 는 이 구현체를 아직 모른다. 따라서 우리는 WebMvcConfigurer 를 구현하는 구현체를 만들어 addArgumentResolvers 메서드를 구현하여 모든 ArgumentResolver 가 모여 있는 곳에 우리가 만들어준 ArgumentResolver를 넣으면 모든 문제가 해결된다.

결론

사소한 문제로 "이러면 어떨까?" "나아지지 않을까?" 라는 생각으로 가볍게 시작했지만, Spring 프레임워크의 구조를 이해하고 애노테이션의 동작과정을 이해할 수 있었다. 잘하는 백엔드 개발자는 설계를 잘 하는 사람이라고 생각한다. 그렇기에 Spring Framwork 와 같은 설계를 볼 때마다 감탄이 나온다. 아직은 누군가가 만들어 놓은 확장성 좋은 설계를 이해하고 따라가기 바쁘지만, 이런 과정을 반복하다 보면 결국 나만의 좋은 설계를 찾아갈 수 있을거라 믿어본다. 오늘도 삽질 완료..

출처

[Java] 어노테이션 (+커스텀 어노테이션 만들기)
https://velog.io/@potato_song/Java-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-%EB%A7%8C%EB%93%A4%EA%B8%B0

Is it possible to get hold of the value of the parameter from MethodParameter object?
https://stackoverflow.com/questions/32132417/is-it-possible-to-get-hold-of-the-value-of-the-parameter-from-methodparameter-ob

Custom Spring annotation for request parameters
https://stackoverflow.com/questions/30715579/custom-spring-annotation-for-request-parameters

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
https://inf.run/YmTZ

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술
https://inf.run/wsKN

profile
평범한 대학생의 코딩일기

0개의 댓글