계좌 목록이 비어 있을 때 주문 화면에서 null 예외가 발생했다. 첫 번째 계좌가 항상 존재한다고 가정하고 선택하는 코드가 원인이었다. 예외를 막고 빈 배열을 반환하자 화면은 멈추지 않았지만 사용자는 버튼만 비활성인 이유를 알 수 없었다.
데이터가 없다는 결과에도 여러 원인이 있었고 다음 행동이 달랐다.
빈 상태의 원인별 구분
아직 로딩 중인지, 정말 계좌가 없는지, 권한이 없는지, 조회가 실패했는지를 구분했다.
public abstract record AccountViewState
{
public record Loading : AccountViewState;
public record Ready(IReadOnlyList<Account> Items) : AccountViewState;
public record NoneAvailable : AccountViewState;
public record Forbidden : AccountViewState;
public record Error(bool CanRetry) : AccountViewState;
}
빈 배열은 NoneAvailable로, 통신 실패는 Error로 바꿔 서로 다른 화면을 그렸다.
다음 행동을 제공하는 팝업
“계좌가 없습니다”만 보여주고 닫기 버튼을 두면 사용자는 다시 같은 화면으로 돌아왔다. 계좌를 만들거나 지원 경로로 이동할 수 있는 경우에는 해당 행동을 제공했고, 앱 안에서 해결할 수 없다면 이유와 돌아갈 화면을 안내했다.
주문 버튼뿐 아니라 빠른 주문, 차트 메뉴 등 모든 진입점에서 같은 규칙을 사용했다. 한 화면만 막으면 다른 경로에서 다시 예외가 났다.
계좌 상태 변경에 따른 자동 복구
다른 화면에서 계좌가 추가된 뒤 돌아오면 이전 빈 상태를 계속 보여주는 문제가 있었다. 화면 재진입이나 계좌 변경 이벤트에서 목록을 다시 확인하고, 유효한 계좌가 생기면 정상 상태로 전환했다.
반대로 마지막 계좌가 비활성화되면 현재 선택과 관련 캐시를 정리했다. 빈 상태는 앱 시작에만 결정되는 값이 아니었다.
예외 방어에서 제품 흐름으로의 확장
처음 수정 목표는 null 예외를 없애는 것이었다. 사용자의 입장에서 보면 중요한 건 왜 진행할 수 없고 다음에 무엇을 해야 하는지였다.
이후 빈 배열을 만나면 단순히 if (items.Count == 0) return;으로 끝내지 않는다. 빈 이유와 가능한 다음 행동, 다른 진입점의 일관성을 함께 본다. 빈 화면도 정상적으로 설계해야 하는 기능 상태였다.
추가 구현 기준
나중에 같은 증상을 다시 만나지 않으려고 문서에 적힌 기준으로 구현을 한 번 더 정리했다.
OWASP ASVS와 API Security 기준을 따라 다시 보면 화면에서 숨겼다는 사실은 권한 검사가 아니다. 계좌나 사용자 식별자를 클라이언트가 보내더라도 서버는 인증 주체가 그 객체에 접근할 수 있는지 매 요청에서 확인해야 한다.
클라이언트의 역할은 권한을 결정하는 것이 아니라 잘못된 조작을 줄이고, 거부 응답을 이전 데이터와 섞지 않으며, 민감한 정보를 로그나 저장소에 남기지 않는 데 가깝다.
다음에 무계좌 화면의 표시 정책 같은 문제를 보면 아래 순서부터 확인하려고 한다.
- 인증 주체와 선택 계좌의 분리
- 서버 권한 거부 시 이전 데이터 제거
- 계좌 없음과 로딩 실패의 구분
- 다른 기기 변경을 반영하는 시점
식별자 형식을 어렵게 만들거나 메뉴를 감추는 방법은 서버의 객체 수준 권한 검사를 대체하지 못한다.
공식 참고 자료
- OWASP ASVS 4.0.3 — 인증·세션·접근제어의 서버 측 검증 기준
- OWASP API Security Top 10 2023 — 객체 수준 권한과 인증·자원 소비 경계