- LoginAttemptService: (아이디+IP) 단위 인메모리 실패 카운트
· 아이디 단독 키 → 잠금 악용 DoS, IP 단독 키 → 공용 IP 연대 잠금. 둘을 묶어 해결
· Clock 주입으로 '5분 후 해제'를 가짜 시계로 테스트 (ClockConfig 추가)
· 5회 도달 시 WARN 로그(보안 사건 추적), 맵 청소로 메모리 누수 방지
- AuthController: 검증 '전에' 잠금 확인(잠금 중엔 정답 확인 기회도 안 줌) → 429
· X-Forwarded-For에서 클라이언트 IP 추출 (Caddy 뒤 전용 — 신뢰 조건 주석화)
- LoginPage: 429 시 '5분 뒤 다시 시도' 안내
- 테스트 5개 추가 → 전체 22개 통과
운영 검증: 5회 실패 → 401, 6회째 → 429 확인. 다른 IP·계정은 영향 없음.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>