교내 팀 프로젝트에서 적용한 내용입니다.
문제 상황
- 우리 서비스에 회원이 되기 위해서는 구글, 카카오 소셜 로그인을 한 후 → 문자 인증을 통해 회원가입이 되는 시스템을 구축하려 했습니다. 문자 인증을 하기 위해서는 알리고 문자 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 야무지게 사용하기
안녕하세요 글 잘 읽었습니다. 저도 문자 인증 구현하는 중인데 문자 API 혹시 뭐 사용하셨는지 여쭤봐도 될까요?