레슨 신청만 누르면 로그아웃되던 버그 — 범인은 411이었다

앱에서 레슨 신청 버튼을 누르면 "로그인이 필요합니다"가 뜨고 세션이 풀린다는 문의가 들어왔다. 재현은 쉬웠지만 원인은 예상과 달랐다.

처음 의심한 것

토큰 만료였다. 신청 API가 다른 API보다 늦게 호출되니 그사이 액세스 토큰이 만료됐고, 갱신 로직이 실패한 것으로 보였다. 그런데 로그를 보니 만료 직후가 아니라 로그인한 지 몇 분 안 된 계정에서도 같은 증상이 났다.

실제로 날아온 응답

프록시 로그를 확인하니 서버가 돌려준 코드는 401이 아니라 411 Length Required 였다. 요청 본문 없이 POST가 나갔고 Content-Length 헤더가 비어 있었다. 서버 앞단이 그걸 거부한 것이다.

문제는 앱 쪽 인터셉터였다.

if (status >= 400 && status < 500) {
  clearSession();   // 4xx = 인증 문제로 간주
  goLogin();
}

4xx를 한 덩어리로 묶어 전부 인증 실패로 처리하고 있었다. 그래서 본문이 빠진 요청 하나가 로그인 세션까지 날려버렸다.

고친 방식

  1. 세션을 지우는 조건을 401로 좁혔다. 403은 권한 문제라 화면에서 안내만 하고, 나머지 4xx는 해당 요청만 실패로 처리한다.
  2. 본문이 빈 POST를 만들지 않도록 요청 빌더에서 body가 undefined면 {}를 넣고 Content-Length를 붙였다.
  3. 인터셉터에 상태코드별 분기 로그를 남겨, 다음에 비슷한 증상이 오면 어떤 코드였는지 바로 보이게 했다.

남은 교훈

"로그아웃된다"는 증상 보고를 인증 문제로 읽는 순간 조사 범위가 인증 모듈로 좁아진다. 실제로는 요청을 만드는 쪽의 실수였고, 인증 코드에는 아무 문제가 없었다. 증상이 아니라 실제 응답 코드부터 확인하는 편이 빨랐다.

비슷한 패턴을 쓰는 앱이라면 4xx 일괄 처리 구간이 있는지 한 번 열어볼 만하다.

트러블슈팅인증HTTP모바일앱
레슨 신청만 누르면 로그아웃되던 버그 — 범인은 411이었다 · peppercode 개발 노트