[상황]
현재 구조에서는 로그인 실패 사유를 자세히 알려주고 있다. 이 구조는 일반 사용자에겐 편리함을 제공하지만 악의적인 사용자에게는 계정 정보를 유추할 수 있는 단서를 제공한다.
[목표]
보안을 강화하기 위해 로그인 실패 시 사유를 노출하지 않고, 내부 로그에만 남기는 구조로 한다.
가능하다면 IP 단위로 실패 횟수 제한을 추가한다.
[실행 계획]
1. 로그인 실패 사유를 로그인 실패로 통일
1-1. 기존 실패 사유는 로그만 남기기
1-2. 노출되는 실패 사유는 전부 로그인 실패로 통일
2. (Optional) IP 단위로 실패 횟수를 제한하거나, ALB/WAF rate-based rule을 추가
1-1. 기존 실패 사유는 로그만 남기기


1-2. 노출되는 실패 사유는 전부 로그인 실패로 통일



2. (Optional) IP 단위로 실패 횟수를 제한하거나, ALB/WAF rate-based rule을 추가
2-1. 트레이드오프 고려
무차별 대입 공격이란?
무차별 대입 공격(Brute Force Attack)은 짧은 시간에 동일한 엔드포인트로 반복 요청을 보내 계정을 탈취하려는 시도다. 방어 방식은 크게 두 가지로 나뉜다. 하나는 애플리케이션 레벨에서 직접 제어하는 방식이고, 다른 하나는 AWS 인프라 레이어에서 차단하는 방식이다.
옵션 1 — 애플리케이션 레벨 IP 기반 실패 횟수 제한
로그인 요청이 들어올 때마다 Redis(또는 인메모리 캐시)에 IP별 실패 카운터를 기록한다. 설정한 임계값을 초과하면 실제 인증 로직을 실행하기 전에 요청을 차단하고 429를 반환한다. 성공 시 카운터를 리셋한다.
흐름: 클라이언트 → Spring Boot → Redis 카운터 조회 → 임계값 초과 시 429 반환
구현 포인트는 아래와 같다.
- login:fail:{ip} 키에 TTL과 함께 카운트를 증가시킨다.
- 인증 로직 실행 전에 차단하므로 불필요한 DB 조회를 막을 수 있다.
- 계정 단위, IP 단위, 계정+IP 복합 등 세밀한 제어가 가능하다.
- 잠금 사유 로깅이나 알림 발송 등 비즈니스 요구사항을 자유롭게 반영할 수 있다.
주의: 분산 환경에서는 인스턴스마다 카운터가 분리되므로 Redis가 필수다. 또한 IPv6 로테이션이나 X-Forwarded-For 스푸핑으로 우회될 수 있고, 대규모 DDoS 상황에서는 요청이 서버까지 도달하므로 효과가 제한적이다.
옵션 2 — AWS WAF Rate-Based Rule
요청이 Spring Boot에 도달하기 전에 AWS WAF가 IP별 RPS를 측정해 임계값을 초과한 IP를 자동으로 차단한다. 서버 코드는 변경할 필요가 없다.
흐름: 클라이언트 → AWS WAF(rate 평가) → 임계값 초과 시 403 반환 (서버 미도달) → ALB → Spring Boot
구현 포인트는 아래와 같다.
- WAF에 Rate-Based Rule을 추가하고 /api/v1/auth/login 경로를 한정 지정한다.
- 5분 윈도우 기준 IP당 최대 요청 수를 설정한다 (최솟값 100 req/5min).
- ALB 리스너에 WAF ACL을 연결하고 CloudWatch로 차단 메트릭을 확인한다.
비용 발생 (WAF ACL 월 $5 + Rule당 $1 + 요청 $0.60/백만). 5분 단위 윈도우라 짧은 버스트 공격에는 반응이 느릴 수 있고, 계정 단위 잠금이나 실패 사유 구분 같은 세밀한 비즈니스 로직은 적용할 수 없다.
2-2. 결론
| 옵션 1 (Redis 카운터) - 선택 | 옵션 2 (AWS WAF rate-based rule) | |
| 차단 위치 | Spring Boot 내부 (서버 도달 후 처리) | AWS WAF (서버 도달 전 차단) |
| 차단 단위 | IP, 계정, IP+계정 복합 모두 가능 | IP 단위만 가능 |
| 윈도우 | TTL로 자유롭게 설정 | 최소 5분 윈도우 (고정) |
| DDoS 대응 | 요청이 서버까지 도달하므로 대규모 공격에 취약 | 서버 미도달로 대규모 공격에 효과적 |
| 비용 | Redis 운영 비용만 발생 | ACL $5/월 + Rule $1 + $0.60/백만 요청 |
| 코드 변경 | 필요 (Spring Boot 구현) | 불필요 (콘솔/IaC 설정만) |
| 비즈니스 로직 | 로깅, 알림, 잠금 사유 구분 등 자유롭게 확장 | 단순 IP 차단만 가능, 세밀한 제어 불가 |
| 테스트 | 단위/통합 테스트 작성 가능 | 코드 레벨 테스트 불가 |
| 우회 위험 | IPv6 로테이션, X-Forwarded-For 스푸핑 | 상대적으로 우회 어려움 |
| 권장 상황 | 계정 단위 잠금, 로그인 실패 이유 추적이 필요한 경우 | 대규모 트래픽 방어, 인프라 레벨 보안이 우선인 경우 |
이유: 포트폴리오 프로젝트에 DDoS 대비까지는..거기다 추가로 들어서 패스.
[결과]
로그인 실패 시 일괄적으로 "로그은 실패: 사용자의 정보가 올바르지 않습니다" 메시지를 클라이언트에 노출시키고, 내부적으로 실패 사유 로깅. 동일 ip에서 연속된 로그인 실패는 Redis에서 카운트하며, 10회 도달 시 5분간 해당 ip로부터의 모든 요청에 429 Too Many Requests 응답 반환.
'Projects > [Final] Shopping Mall Project' 카테고리의 다른 글
| [기능구현] 최종 결제 및 주문 유일성 제약 적용하기 (1부) (0) | 2026.04.27 |
|---|---|
| [트러블슈팅] CD환경에서만 ObjectMapper 빈 등록이 실패하는 이유 (0) | 2026.04.27 |
| [트러블슈팅] 로그아웃 API에서 인증, 무효화 구현하기 (0) | 2026.04.27 |
| [테스트] influxdb + grafana로 k6 부하 테스트 실행하기 (0) | 2026.04.23 |
| [QueryDsl] 기간별 판매 통계 조회 메서드 테스트 코드 작성 및 수정하기 (0) | 2026.04.22 |