관리자 OTP 재발송 폭주를 레이트 리밋으로 차단해 발송량 40% 절감
목차
관리자 2FA 재발송 폭주 막기
관리자 로그인 흐름을 다시 들여다봤음. 진입할 때 OTP 발송하는 구간이 있는데, 사용자가 "재발송" 버튼을 연타하면 그대로 발송 API가 비례해서 호출됨. 비용도 비용이지만, 수신자 입장에서도 같은 코드가 반복으로 도착해서 어떤 게 유효한지 헷갈림.
기존 동작의 문제
- 발송 직후 즉시 재발송 가능 — 1초 안에 5번 누르면 5건 발송됨
- 잘못 입력해서 재시도하는 경우 카운트 누적 안 됨 — 무한 시도 가능
- 코드 만료 전에 재발송하면 기존 코드도 살아있어서 어떤 코드를 쳐야 통과되는지 사용자도 헷갈림
컨트롤러가 인증 코드 발급/검증/재발송을 모두 들고 있었음. 책임 분리도 안 되고, 동시 요청 제어가 어디서도 일어나지 않았음.
개선 포인트
| 항목 | 이전 | 변경 후 |
|---|---|---|
| 재발송 최소 간격 | 없음 | 30초 |
| 시도 횟수 제한 | 없음 | 5회 / 10분 |
| 코드 발급 시 기존 코드 | 공존 | 즉시 무효화 |
| 잠금 해제 | 자동 만료만 | TTL 기반 + 카운터 리셋 |
레이트 리밋은 캐시 계층에 키 박아서 처리했음. 발송 시점에 TTL 30초 걸어두고, 재발송 요청이 들어오면 키 존재 여부로 차단.
val key = "auth:otp:resend:${adminId}"
if (cache.exists(key)) {
throw TooManyRequests("30초 후 다시 시도")
}
cache.setex(key, 30, "1")
sendOtp(adminId)
시도 횟수는 별도 키로 카운트 누적, 5회 도달하면 10분 락. 락 풀리면 카운터 0으로 리셋.
컨트롤러 정리하면서 얻은 것
기존엔 인증 관련 메서드 4개가 한 컨트롤러에 섞여 있었음. 이번에 분리하면서 책임을 쪼갰음.
- 관리자 로그인 진입 — 자격 확인만
- OTP 발급 — 발송 책임만
- OTP 검증 — 검증과 세션 발급만
- OTP 재발송 — 레이트 리밋 + 재발급
책임이 쪼개지니까 테스트도 단순해짐. 발급 로직 테스트할 때 검증 로직 mock 안 해도 됨.
운영에서 본 효과
배포 다음 날 발송량 모니터링했더니 평소 대비 약 40% 감소. 같은 관리자가 2분 안에 3건 이상 받던 케이스가 거의 사라짐. 운영 담당자 쪽에서도 "어떤 코드를 쳐야 되지?" 하는 문의가 줄었다고 함.
회고하면서 느낀 건, 외부 발송 비용 들어가는 구간은 처음부터 레이트 리밋 박고 가는 게 정답이라는 것. 나중에 붙이면 이미 사용자 행동 패턴이 굳어서 추가 클레임이 따라옴.
다음
댓글 0
첫 댓글 달아줘.