로그인 코드가 토큰 발급뿐 아니라 계좌 조회와 실시간 연결 주소까지 직접 처리하고 있었다. API 오류가 나면 로그인 실패로 보였고, 토큰 갱신을 수정할 때 여러 업무 요청이 영향을 받았다. 서버 주소가 다르지만 HTTP 요청이라는 이유로 한 클래스에 모여 있었다.
네트워크 기술보다 인증, 세션, 업무 데이터라는 책임을 기준으로 경계를 다시 봤다.
토큰 발급과 사용의 분리
인증 클라이언트는 로그인과 갱신 결과를 세션 저장소에 전달하고, 업무 API 클라이언트는 유효한 세션을 받아 요청했다.
public interface IAuthClient
{
Task<Session> LoginAsync(LoginInput input);
Task<Session> RefreshAsync(RefreshProof proof);
}
public interface IAccountApi
{
Task<IReadOnlyList<Account>> GetAccountsAsync(Session session);
}
화면이 토큰 문자열이나 인증 헤더를 직접 조립하지 않게 했다.
공통 전송과 서버별 정책의 구분
타임아웃, 요청 ID, 안전한 로깅은 공통 HTTP 계층에서 제공했다. 기본 URL, 인증 방식, 오류 코드 해석은 각 서버 클라이언트가 맡았다.
401을 모두 같은 의미로 보지 않고 인증 서버의 자격 증명 거부와 업무 서버의 세션 만료를 계약에 맞춰 변환했다.
갱신 동시 실행의 단일 진입점
여러 API가 동시에 만료를 발견하면 각자 토큰 갱신을 시작할 수 있었다. 세션 관리자가 한 번의 갱신 작업을 공유하고 성공한 요청만 정책에 따라 재시도했다.
중복 실행 영향이 있는 요청은 자동 재시도하지 않고 결과 확인 경로를 사용했다. 새 세션 발급과 업무 요청 재실행은 별도 결정이었다.
테스트에서는 인증 서버만 실패, 업무 API만 실패, 갱신 중 여러 조회 발생, 로그인 성공 뒤 첫 업무 조회 실패를 나눴다. 경계를 나눈 구조가 사용자 안내와 재시도 범위에서도 실제로 분리되는지 확인했다.
네트워크 로그의 헤더와 본문에는 토큰이 들어갈 수 있어 공통 로거에서 허용 필드만 남겼다. 클라이언트를 나눈다고 비밀 정보 처리 기준까지 제각각이 되면 안 됐다.
책임 경계 기반 오류 분류
기존에는 “로그인 실패” 안에 서버 연결, 자격 증명, 계좌 조회 오류가 섞였다. 책임을 나누니 사용자가 다시 입력해야 하는지, 네트워크를 기다려야 하는지, 일부 화면만 재시도하면 되는지가 분명해졌다.
이 경험 뒤 API 클래스를 URL이나 프로토콜만으로 묶지 않는다. 누가 자격을 만들고 누가 그 자격으로 어떤 업무를 수행하는지 본다. 경계가 명확하면 변경 영향과 실패 범위도 작아졌다.
추가 구현 기준
작업 당시에는 눈앞의 오류를 먼저 고쳤지만, 이후 기술 문서와 맞춰 보며 확인 순서를 조금 더 구체화했다.
OWASP ASVS와 API Security 기준을 따라 다시 보면 화면에서 숨겼다는 사실은 권한 검사가 아니다. 계좌나 사용자 식별자를 클라이언트가 보내더라도 서버는 인증 주체가 그 객체에 접근할 수 있는지 매 요청에서 확인해야 한다.
클라이언트의 역할은 권한을 결정하는 것이 아니라 잘못된 조작을 줄이고, 거부 응답을 이전 데이터와 섞지 않으며, 민감한 정보를 로그나 저장소에 남기지 않는 데 가깝다.
다음에 로그인 서버·API 호출 책임 분리 같은 문제를 보면 아래 순서부터 확인하려고 한다.
- 익명·인증 중·인증됨·만료 상태
- 재시도 가능한 오류와 사용자 입력 필요 오류
- 토큰 폐기와 화면 초기화 순서
- 동시에 도착한 만료 응답의 단일 처리
식별자 형식을 어렵게 만들거나 메뉴를 감추는 방법은 서버의 객체 수준 권한 검사를 대체하지 못한다.
공식 참고 자료
- OWASP ASVS 4.0.3 — 인증·세션·접근제어의 서버 측 검증 기준
- OWASP API Security Top 10 2023 — 객체 수준 권한과 인증·자원 소비 경계