Cheondi
개발 · API

로그인 서버와 API 호출 책임을 나누다

인증 토큰 발급과 업무 API 호출이 한 모듈에 섞인 구조를 책임별 클라이언트로 나눈 기록입니다.

  • #api
  • #authentication
  • #architecture

로그인 코드가 토큰 발급뿐 아니라 계좌 조회와 실시간 연결 주소까지 직접 처리하고 있었다. 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이나 프로토콜만으로 묶지 않는다. 누가 자격을 만들고 누가 그 자격으로 어떤 업무를 수행하는지 본다. 경계가 명확하면 변경 영향과 실패 범위도 작아졌다.