원격지원 버튼 조건 오류 수정과 블랙리스트 자동 차단 구조 도입
목차
list.jsp에서 원격지원 버튼이 제대로 노출되지 않는 문제가 올라왔다. 재현 환경을 로컬에 맞춰 놓고 보니 원인은 linkStatus 조건이 버튼 렌더링 분기에 끼어 있었던 것. 연결 상태를 체크하는 조건이 버튼 표시 여부까지 영향을 주고 있어서, 특정 상태값에서 버튼이 아예 숨겨졌다.
조건 하나가 만드는 운영 구멍
JSP 기반 화면에서 이런 류의 버그가 자주 나오는 이유가 있다. EL 표현식이나 JSTL 조건문이 중첩될수록 의도치 않은 단락 평가가 발생한다. 예를 들어 아래처럼 조건을 나열했을 때,
<c:if test="${item.linkStatus eq 'Y' and item.useYn eq 'Y'}">
<button class="btn-remote">원격지원</button>
</c:if>
linkStatus가 null이거나 다른 문자열이면 버튼 자체가 사라진다. 원격지원 버튼이 linkStatus랑 논리적으로 엮여야 할 이유가 없는데도, 어느 시점에 조건이 추가되면서 같이 묶인 거다. 이번 수정에서는 linkStatus 조건을 버튼 분기에서 제거했다. 버튼 노출 여부는 별개의 권한/상태 체크로 처리하는 게 맞다.
수정 범위는 작았다. list.jsp 해당 분기 한 줄. 하지만 이걸 확인하기 전에 로그 추적, 조건 흐름 파악, 다른 연관 화면에 사이드 이펙트 없는지 훑는 데 시간이 더 걸렸다. 수정보다 파악이 오래 걸리는 게 이 종류 버그의 특성이다.
자주 보이는 버그 패턴을 정리해두면 다음 번에 빠르게 좁힐 수 있다.
| 패턴 | 증상 | 첫 확인 포인트 |
|---|---|---|
| null 체크 순서 오류 | NPE (NullPointerException) | 스택트레이스 최상단 라인 |
| 경로 예외 처리 누락 | 필터에서 정상 요청 차단 | 필터 체인 순서 |
| YAML 공백 혼용 | 설정값 파싱 실패 | 들여쓰기 문자 타입 |
| EL/JSTL 조건 중첩 | UI 요소 누락·비정상 노출 | 조건 단락 평가 순서 |
| import 누락 | 컴파일 에러 | 빌드 로그 |
이번 케이스는 네 번째에 해당했다. 루틴처럼 패턴표 보면서 범위 좁히는 게 익숙해지니까 첫 파악 시간이 줄긴 한다. 그래도 매번 새 조합이 나온다는 게 함정이긴 하다.
블랙리스트 자동 등록 구조 도입
같은 날 공격성 요청 차단 구조도 손봤다. 기존에는 수상한 요청을 감지해도 수동으로 IP를 블랙리스트에 올려야 했다. 대응 속도가 느릴 수밖에 없고, 자동화된 스캐너 공격에는 특히 늦다.
이번에 만든 구조는 간단하다. 감지 레이어에서 임계값 초과 또는 악성 패턴 매칭 시 DB에 자동 등록하고, 이후 요청은 필터 단에서 즉시 차단한다. 흐름을 간략하게 표현하면 이렇다.
요청 수신
→ Rate Limit / 패턴 필터 체크
→ 임계값 초과 시 → DB blacklist 자동 INSERT (TTL 포함)
→ 이후 동일 IP/키 → 필터 최상단에서 403 반환
자동 등록이다 보니 오탐 처리를 신경 쓰지 않으면 운영 중에 정상 사용자를 막는 상황이 생긴다. 몇 가지 기준을 잡았다.
- 배치 작업이나 내부 로드 테스트 IP는 화이트리스트로 사전 제외
- 블랙리스트 등록 시 TTL 설정 필수. 영구 차단은 오탐 복구가 안 된다
- 자동 등록 이벤트마다 Slack 알림 연동. 차단 건수가 튀면 오탐인지 공격인지 빠르게 판단
TTL은 초기에 짧게 잡고 실 운영에서 늘리는 방향이 좋다. 너무 길게 시작하면 오탐 발생 시 복구까지 시간이 오래 걸린다.
Rate Limit 구성할 때 매번 하는 고민
Rate Limit은 수치 설정보다 기준 정의가 더 어렵다. "얼마나 많이 막을 것인가"보다 "정상 사용자 트래픽 패턴이 어디까지인가"를 먼저 파악해야 한다. 이걸 모르고 수치를 먼저 잡으면 타이트하게 설정해서 정상 사용자가 막히거나, 느슨하게 설정해서 의미 없는 필터가 된다.
Redis 슬라이딩 윈도우 방식은 고정 윈도우 대비 경계 구간에서 버스팅 공격이 어렵다. 고정 윈도우는 창 경계 시점 직전/직후로 요청을 몰면 두 배까지 허용되는 구조적 취약점이 있다. 슬라이딩 윈도우는 항상 최근 N초를 기준으로 카운트하기 때문에 이 문제가 없다.
단, Redis를 Rate Limit 저장소로 쓸 때 Redis가 단일 장애점이 되지 않도록 연결 풀 설정과 타임아웃을 반드시 같이 챙겨야 한다. Redis 응답이 느려지면 Rate Limit 체크 자체가 병목이 되고, 최악의 경우 전체 요청 처리가 지연된다. 타임아웃을 짧게 잡고 Redis 장애 시에는 차단 없이 통과시키는 페일오픈 전략이 현실적으로 낫다. 페일클로즈는 Redis 이슈 때마다 서비스 전체가 멈추는 상황을 만든다.
이번 두 작업 모두 규모 자체는 크지 않았다. 버튼 조건 한 줄 제거, 자동 차단 흐름 연결. 그런데 이런 작업들이 쌓이면서 시스템이 실제로 덜 깨지고 덜 뚫리게 된다. 드라마틱한 변화보다 이런 것들이 운영 안정성에 더 직접적으로 기여한다는 걸 팀장 포지션이 되고 나서 더 실감하고 있다.
댓글 0
첫 댓글 달아줘.