이중 요청 방어 전략 개요
이중 요청 방어는 클라이언트 측 제어, 백엔드 측 제어, 그리고 클라이언트와 백엔드의 연동을 통한 제어로 구분할 수 있습니다. 각 접근 방식은 고유한 장단점을 가지며, 애플리케이션의 특성과 중요도에 따라 적절히 조합하여 적용해야 합니다.
클라이언트 측 방어
- 서브밋 버튼 제어: 가장 기본적인 방법으로, 요청 전송 시 버튼을 비활성화하여 중복 클릭을 방지합니다. Rails UJS(5.0-6.1)에서는
data-with속성을 사용했으며, 현재는 Stimulus 등으로 직접 구현합니다.
백엔드 측 방어
-
PRG(Post-Redirect-Get) 패턴: POST 요청 완료 후 HTML을 직접 반환하는 대신 결과 페이지로 리다이렉트하여 브라우저 새로고침으로 인한 이중 요청을 방지합니다.
-
배타 제어(Exclusive Control): 특정 리소스에 대한 동시 접근을 제한하여 데이터 불일치를 방지합니다. Active Record의
with_lock을 활용한 비관적 락(Pessimistic Lock), Advisory Lock, Redis를 이용한 락, 또는 FIFO(First-In, First-Out) 큐를 활용하는 방법이 있습니다. 락 해제 후의 대응과 불필요한 리소스에 대한 락 사용은 주의해야 합니다. -
테이블 설계: 이중 요청 발생 시에도 데이터 무결성을 유지할 수 있도록 테이블을 설계합니다. 상태 전이 규칙(State Transition Rule)을 정의하거나, 고유 제약(Unique Constraint)을 활용하여 중복 요청 시 데이터베이스 레벨에서 오류를 발생시킵니다. AASM Gem을 활용하여 상태 전이를 관리할 수 있습니다.
-
레이트 리미트(Rate Limit): 일정 기간 동안 처리할 수 있는 요청 수를 제한하여 시스템 과부하를 방지하고 이중 요청을 제어합니다. 사용자 ID 등을 키로 사용하여 특정 API의 호출 횟수를 제한할 수 있으며, Rack Attack, Throttling 등의 Gem을 활용할 수 있습니다.
-
API 캐시: 이전에 처리된 요청의 응답을 캐시하여 동일한 요청이 다시 올 경우 캐시된 응답을 반환함으로써 멱등성을 보장합니다. 클라이언트 관점에서는 모두 성공으로 처리되지만, 캐시 키 설계, 유효 기간, 응답 크기, 캐시 저장 전 중복 요청 처리 등의 어려움이 있습니다.
클라이언트-백엔드 연동 방어
-
멱등성 키 헤더(Idempotency Key Header): 클라이언트가 요청에 고유 식별 키를 부여하여 전송하고, 서버는 이 키를 기반으로 요청의 멱등성을 보장합니다. API 캐시보다 더 복잡하지만, 유연하고 견고한 API 서버 구축이 가능하며, 미처리/처리 중/처리 완료 등 세부 상태 관리가 가능하여 악의적인 리트라이와 우발적인 중복 요청을 구분할 수 있습니다.
-
Etag 및 If-Match: Etag는 리소스의 버전을 태그로 관리하고, 클라이언트가 If-Match 헤더를 통해 특정 버전과 일치할 경우에만 요청을 처리하도록 합니다. 이는 낙관적 락(Optimistic Lock) 방식과 유사하며, 동시 업데이트 방지에 효과적입니다. 변경 빈도가 낮은 리소스(예: 사용자 프로필)에 적합합니다.
-
원타임 토큰(One-time Token): 한 번만 사용 가능한 토큰을 발행하여 이중 요청을 방지하고, 정당한 요청임을 보증하여 리플레이 공격(Replay Attack)과 같은 부정 행위를 방지합니다. CSRF 토큰과 유사하지만, 토큰의 일회성이 필수적입니다.
One Bank의 이중 요청 방어 사례
-
멱등성 키 헤더: 외부 서비스와의 연동(결제, 입출금)과 같이 복잡하고 데이터 불일치 위험이 큰 중요 작업에 사용됩니다. 우발적인 이중 요청과 의도적인 다른 요청을 구분하여 처리합니다.
-
원타임 토큰: 패스코드 등록, 카드 번호 표시 등 보안 강도가 높은 앱 내 중요 처리에 적용됩니다. 리플레이 공격 방지가 주 목적이며, 이중 요청 방지를 위해 멱등성 키와 함께 사용되는 경우도 많습니다.
-
레이트 리미트: 가상 카드 재발급과 같이 비정상적인 빈도로 요청될 가능성이 있는 작업에 적용됩니다. 낮은 비용으로 잠재적 문제를 해결할 수 있습니다.
-
테이블 설계 + 캐시: 외부 서비스 웹훅 처리와 같이 비동기적이고 리트라이가 발생하는 상황에서 데이터 일관성을 유지하고 리트라이 루프를 방지하는 데 활용됩니다. 외부 서비스의 ID를 기반으로 멱등성을 보장합니다.