..

Rate Limit과 Fail Limit은 어디까지가 적절할까?

최근 account 서비스에 친구 초대 코드 인증 API가 추가되면서, 해당 API에 대한 rate limit / fail limit 기준을 어떻게 가져가야 할지 고민을 했다.

초대 코드 인증은 회원가입 단계에서 호출되며, 특성상 외부 공격(브루트포스, 봇 트래픽)에 가장 먼저 노출되는 지점이기에 더 관심 있게 봤어야 했다. 이번 글에서는 실제 논의 과정과 로그 분석을 바탕으로, 어떤 기준으로 제한 정책을 잡았는지 정리해보려 한다.


왜 제한이 필요했을까?

이번에 방어하고자 했던 케이스는 크게 두 가지였다.

  1. 초대를 받지 않은 사용자가 초대 코드를 브루트포스로 시도하는 경우
  2. 회원이 아닌 사용자가 과도하게 API 요청을 보내는 경우

이미 account 서비스 전반에는 5분당 100회 수준의 rate limit이 설정되어 있었지만, 초대 코드 인증 API는 다음과 같은 특징을 가진다.

  • 인증 이전 단계 (userId 없음)
  • 성공/실패가 명확한 API
  • 코드 값이 존재 → 브루트포스 타겟이 되기 쉬움

즉, 과도하게 사용될 수 있는 API라고 판단을 했고 이에 대한 대비로 rateLimit, failLimit을 걸고자 했다.


처음에 생각했던 구조

가장 먼저 떠올린 방식은 다음과 같은 2단 방어 구조였다.

[API Rate Limit]
  ├─ IP 기준 (1분 10회)
        ↓ 통과
[Referral Attempt Guard]
  ├─ 실패 카운트 누적 (Redis)
  ├─ 5회 연속 실패 → 10분 쿨다운
  └─ 성공 시 fail_count = 0
        ↓ 통과
[Referral Code Validation]
  • Edge 단에서 Rate Limit
  • App 단에서 Fail Limit
  • 성공 시에는 실패 카운트 초기화

구조 자체는 단순하지만, “과연 이 limit과 duration의 수치가 적절한지”에 대한 확신은 없었다.

  • 너무 강하다면 일반 사용자에게 걸림돌이 될 수 있고
  • 너무 약하다면 악용하는 사용자가 많아질 것이었다.

그래서 내부적으로만의 판단이 아닌 보안 팀의 의견을 받아 수치를 정하기로 하였다.


실제 트래픽은 어땠을까?

앱 로그를 충분히 확보하지 못한 상태라, Cloudflare 로그를 기준으로 과거 데이터를 확인해봤다.

특히 21일 이후 가입자가 급증했던 시점의 데이터를 살펴보면,

  • 봇으로 보이는 IP들은 signup 경로를 1분에 약 5~6회 정도 반복 호출하고 있었다.

유저 플로우상 signup친구 초대 인증은 호출 빈도가 비슷하다고 가정하면,

  • 1분 10회 제한은 사람 기준으로는 충분히 여유롭고, 봇 기준으로는 애매한 수치라는 판단이 들었다.

또 흥미로웠던 점은 시계열을 늘려서 보면 더 명확했다.

  • 10분 기준 50회 이상 호출하는 IP는 Cloudflare 기준에서도 거의 봇 트래픽으로 분류된다고 했다.

추가의견

다른 보안엔지니어 분의 말씀으로는 아래와 같은기준으로 가져가면 좋을 것 같다고 했다.

1. API Rate Limit

  • IP: 1분 10회(유지 가능) + 초당 burst 제한 추가

2. Referral Attempt Guard (App)

  • 키: ip + device_id 우선, 가능하면 account_id도 추가
  • 실패 판단: 10분 윈도우 내 실패 5회 → 10분 쿨다운
  • 성공 처리: fail_count = max(0, fail_count - 1) 또는 TTL 기반 감쇠
  • 단계형 백오프(가능하면)

3. Referral Code Validation

  • 외부 응답은 실패 사유 통일
  • 내부적으로만 실패 사유 로깅/지표화 (편집됨)

최종 결정

위와 같은 내용을 토대로, 최종적으로 아래 기준으로 진행하기로 결정했다.

1. Rate Limit

  • IP 기준
  • 10분 50회

2. Fail Limit

  • 키: ip 또는 email

    • (userId 생성 이전 단계이므로)
  • 기준:

    • 10분(마지막 시도 기준) 내 5회 연속 실패10분 쿨다운
  • 성공 시:

    • 실패 카운트 0으로 초기화

3. 에러 로깅

  • 외부 응답: 동일한 에러 메시지 (외부에서 정확한 기준을 알지 못하게 하기 위해)
  • 내부 로깅: 실패 사유 분리 기록

4. Burst Limit (검토 중)

  • 초당 2~3회 현재 앱에서 적용 가능한지 확인 후

마무리하며

Rate limit과 fail limit은 단순히 “얼마로 걸까?”의 문제가 아니라,

  • 유저 플로우
  • 실제 트래픽 패턴
  • 공격자가 어떤 식으로 시도할지

를 함께 고려해야 한다는 점을 다시 한 번 느꼈습니다.

특히 이번처럼 인증 전 단계 API에서는 단순한 Rate limit만이 아닌 Fail limit도 함께 추가하여 역할을 나눠 방어하는 구조가 꽤 효과적이라는 인상을 받았습니다.