본문 바로가기
spring

spring RequestBodyAdviceAdapter를 통한 apiKey 인증인가 적용

by 토망이 2023. 6. 4.

본 글에서는 이전 글에서 적용한 apiKey를 통한 인증인가 방식을, Spring Filter가 아니라 RequestBodyAdviceAdapter라는 Spring에서 제공하는 일종의 aop를 활용해보고자 합니다. 자세한 내용은 이전 글을 참고해주세요.

Filter에 @RequestBody불러오기

 

spring 파라미터와 Request정보가 같이 필요할 때 처리방법(2) - Filter에 @RequestBody 불러오기

이전글에서 언급한 Filter에 @RequestBody 데이터 가져오기에 대한 구현을 직접 해보겠습니다. 이전글을 참고해주세요. spring 파라미터와 Request정보가 같이 필요할 때 처리방법(1) spring 파라미터와 Requ

dnl1029.tistory.com

 

Spring Filter에 구현했던 코드보다 훨씬 적게 클래스 하나만 구현하여 모든 restapi 요청에 동일한 인증/인가를 적용하여 개인적으로는 가장 깔끔한 방법이라 생각합니다. 그런데 아래 어노테이션을 통해 모든 RestController에 적용하기 때문에, 필터에서 url로 적용할 요청들을 지정하는 것과 달리 모든 rest api 요청에 적용되는 aop라 사용함에 있어서 주의가 필요할것 같습니다.

@ControllerAdvice(annotations = RestController.class)

 

- RequestBodyAdviceAdapter 

해당 클래스를 상속받고, 총 4개의 Method를 Overide하였습니다. 간단하게 메서드에 대한 설명을 하면

1) afterBodyRead : springMVC가 RequestBody를 읽고나서 실행되는 메서드이며, 파라미터의 Object는 @RequestBody에 사용되는 객체를 의미함.

2) beforeBodyRead : springMVC가 RequestBody를 읽기전에 사용

3) handleEmptyBody : requestBody가 비워져있을때 사용할 디폴트값

4) supports : return값이 true여야 요청이 Controller로 넘어가니, 적용이 필요할때 꼭 true로 바꿔줘야 합니다.

 

Filter때와 마찬가지로 객체에서 ID같은 정보를 뽑아, DB에 저장된 값과 매핑하여 apiKey를 검증하는 부분을 생략하고, 단순하게 @RequestBody에 들어오는 apiKey라는 필드와, Request의 헤더에 "apiKey"로 들어오는 value가 동일한지를 검증하였습니다.

@Slf4j
@ControllerAdvice(annotations = RestController.class)
public class MyRequestBodyAdviceAdapter extends RequestBodyAdviceAdapter {

    //springMVC가 RequestBody를 읽은 후 실행되는 Method. 여기서 Object가 Controller에서 @RequestBody로 받는 객체를 의미
    @Override
    public Object afterBodyRead(final Object body,
                                final HttpInputMessage inputMessage,
                                final MethodParameter parameter,
                                final Type targetType,
                                final Class<? extends HttpMessageConverter<?>> converterType) {
        //AdviceAdapter에서 Request 정보 불러오기 위해 추가
        HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

        String apiKey = req.getHeader("apikey");

        ApiKeyDto apiKeyDto = (ApiKeyDto) body;
        log.info("afterBodyRead. apiKeyDto : {}",apiKeyDto);

        String testkey = apiKeyDto.getApiKey();

        if(!testkey.equals(apiKey)) {
            throw new RuntimeException("MyRequestBodyAdviceAdapter. invalid api key");
        }
        return body;
    }

    //spring MVC가 RequestBody 읽기전에 실행되는 Method

    @Override
    public HttpInputMessage beforeBodyRead(final HttpInputMessage inputMessage,
                                           final MethodParameter parameter,
                                           final Type targetType,
                                           Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        log.info("beforeBodyRead");
        return inputMessage;
    }

    //requestBody가 비워져있을때 디폴트값 넣을수 있음.
   @Override
   public Object handleEmptyBody(final Object body,
                                 final HttpInputMessage inputMessage,
                                 final MethodParameter parameter,
                                 final Type targetType,
                                 final Class<? extends HttpMessageConverter<?>> converterType) {
        return null;
   }

    //이게 true여야 컨트롤러로 넘어가고, false면 요청이 Controller로 넘어가지 않음.
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }
}

 

 

Postman을 통해 restapi 를 날려보겠습니다.(http://localhost:8080/api/apikey/test) 

Body에는 아래와 같이 넣고

헤더도 동일하게 넣으면

로그는 아래와 같이 정상적으로 찍힙니다.

 

 

이번에는 헤더를 일부러 변경해보았습니다.

 

아래와 같이 RuntimeException이 발생함을 확인할 수 있습니다.

댓글