Interceptor로 문자 인증 요청 횟수 제한하기 - 근데 레디스를 살짝 곁들인

박준수·2024년 2월 3일

Archive

목록 보기
1/7

교내 팀 프로젝트에서 적용한 내용입니다.

문제 상황

  • 우리 서비스에 회원이 되기 위해서는 구글, 카카오 소셜 로그인을 한 후 → 문자 인증을 통해 회원가입이 되는 시스템을 구축하려 했습니다. 문자 인증을 하기 위해서는 알리고 문자 API를 사용하였습니다. 4자리의 코드를 랜덤으로 생성을 해서 사용자에게 본인 확인 인증 번호를 보네줍니다.
  • 알리고 문자 API는 건당 8.4원의 비용이 발생하고, 인증되지 않은 회원이 무제한으로 문자 전송을 받는 것은 보안에 위험이 발생할 수 있습니다.

문제 고민

  • 문자 인증 엔드포인트에 요청 횟수가 5번이 넘어가게 되면 예외가 발생하고, 24시간 즉, 하루 뒤에 다시 요청을 해달라는 메시지를 보네주고 싶었습니다.
  • 문자 인증 엔드포인트에 요청하는 횟수를 저장하기 위해서는 인메모리 데이터베이스인 Redis를 사용하면 좋을 것 같습니다. → 데이터를 메모리에 저장하고 관리하므로 매우 빠른 읽기와 쓰기 작업을 가능하기 때문입니다. 또한 24시간이라는 시간 개념을 도입을 할려면 Mysql 디스크 기반의 데이터베이스를 사용하는 것은 불필요하게 성능의 영향을 끼칠 수 있습니다.

해결 방안

  • 문자 인증 엔드포인트에 인터셉터를 적용하면 Controller에서 요청을 처리하기 전에 문제에 대한 해결을 할 수 있습니다.

HandlerInterceptor 인터페이스

  • Interceptor를 HandlerInterceptor 인터페이스를 구현하여 사용할 수 있습니다.
  • HandlerInterceptor은 org.springframework.web.servlet 안에 있습니다.
  • HandlerInterceptor은 preHandle, postHandle, afterCompletion을 구현하도록 명시되어 있습니다.

  • preHandle : 핸들러 실행 전의 차단 지점입니다. HandlerMapping이 적절한 핸들러 객체를 결정한 후, HandlerAdapter가 핸들러를 호출하기 전에 호출됩니다.
    DispatcherServlet은 핸들러 자체가 끝에 있는 여러 개의 인터셉터로 구성된 실행 체인에서 핸들러를 처리합니다. 이 방법을 사용하면 각 인터셉터는 실행 체인을 중단하기로 결정할 수 있으며 일반적으로 HTTP 오류를 보내거나 사용자 정의 응답을 작성합니다.
  • postHandle : 핸들러가 성공적으로 실행된 후의 차단 지점입니다. HandlerAdapter가 실제로 핸들러를 호출한 후 호출되지만 DispatcherServlet이 뷰를 렌더링하기 전에 호출됩니다. 지정된 ModelAndView를 통해 추가 모델 객체를 뷰에 노출할 수 있습니다. DispatcherServlet은 핸들러 자체가 끝에 있는 여러 개의 인터셉터로 구성된 실행 체인에서 핸들러를 처리합니다. 이 방법을 사용하면 각 인터셉터가 실행을 사후 처리하여 실행 체인의 역순으로 적용될 수 있습니다.
  • afterCompletion : 요청 처리 완료 후, 즉 뷰 렌더링 후 콜백입니다. 핸들러 실행 결과에 대해 호출되므로 적절한 리소스 정리가 가능합니다.
    참고: 이 인터셉터의 preHandle 메소드가 성공적으로 완료되고 true를 반환한 경우에만 호출됩니다!
    postHandle 메소드와 마찬가지로 이 메소드는 체인의 각 인터셉터에서 역순으로 호출되므로 첫 번째 인터셉터가 마지막으로 호출됩니다.

구현 방법

  • WebMvcConfig.class

  • 애플리케이션 내에서 인터셉터가 작동할 수 있도록 빈(Bean)으로 등록해줘야 합니다.

  • WebMvcConfigurer 인터페이스 : @EnableWebMvc가 제공하는 빈을 커스터마이징할 수 있는 기능을 제공하는 인터페이스

  • addInterceptors() : 애플리케이션 내에 인터셉터를 등록해주는 기능

    • addPathPatterns( )는 인터셉터를 호출하는 주소와 경로를 추가하는 개념입니다.
    • excludePathPatterns( )는 주소와 경로를 인터셉터 호출에서 제외합니다.
  • ApiLimitCheckInterceptor.class

  • ApiLimitCheckInterceptor은 HandlerInterceptor를 상속받아 preHandler를 오버라이딩 합니다. 문자 인증 Controller(핸들러)로 가기 전에 문자 인증 횟수를 체크하기 위해서이기 때문입니다.

  • memberId로부터 Redis에 저장된 API요청 횟수를 가져와서 5번이 넘게 되면 Runtime 예외를 터지게 합니다.

  • API요청 횟수가 처음이라면 레디스에 저장을 하고, 5번 이내이면 횟수를 증가시키면서 true를 반환합니다.

  • ApiUsageCacheRepository.class

  • redisTemplate를 주입받고 해쉬 타입으로 데이터를 넣고, 증가시키고, 가져옵니다.
  • 해쉬 타입으로 한 이유는 혹시 모를 확장성을 생각했습니다.
  • 요청을 할 때마다 사용자의 요청 횟수를 증가 시켜 줘야 하는데 이때, 키를 통해 delete하고, +1을 증가시켜 put을 해줄 수 있지만 increment를 통해 +1 씩 증가 시킬 수 있습니다. delete 하고 put을 하는 것은 호출 횟수가 증가하는 것도 문제지만 redis의 DEL 함수의 시간 복잡도가 O(N)이고 INCR 함수가 O(1)이기도 합니다.

  • 레디스에서 시간 복잡도 O(N)을 사용하면 위험한 이유는 레디스가 기본적으로 Single Thread이기 때문에 O(1) 시간 복잡도를 사용하는 것이 좋습니다.

참고 자료

[Spring] 설정 자동화와 설정의 변경, @EnableWebMvc와 WebMvcConfigurer

Interception :: Spring Framework

Interceptor 개념 및 흐름

Spring ArgumentResolver와 Interceptor

DEL

INCR

[NHN FORWARD 2021] Redis 야무지게 사용하기

profile
방구석개발자

1개의 댓글

comment-user-thumbnail
2024년 11월 6일

안녕하세요 글 잘 읽었습니다. 저도 문자 인증 구현하는 중인데 문자 API 혹시 뭐 사용하셨는지 여쭤봐도 될까요?

답글 달기