API 개발을 하다 보면 한 번쯤 이런 상황을 마주친다. 분명히 서버가 요청을 거부한 건데, 돌아오는 응답은 403이 아니라 404다. 처음엔 당연히 버그라고 생각한다. 그런데 이게 버그가 아니라 의도적인 설계 선택이라면? HTTP 상태 코드를 스펙대로만 외웠던 사람이라면, 이 부분이 가장 헷갈리는 지점이다.
403을 반환하면 뭐가 문제인가

403 Forbidden은 "이 리소스는 존재하지만, 너에게는 접근 권한이 없다"는 의미다. 교과서적으로는 맞는 응답이다. 클라이언트가 권한 없이 어떤 리소스에 접근하려 할 때 서버는 403을 돌려주고, 클라이언트는 "아, 내가 권한이 없구나"라고 인식한다.
문제는 그 인식의 부작용이다. 403을 받은 순간, 클라이언트는 한 가지 정보를 얻는다. 이 리소스가 실제로 존재한다는 사실. 단순한 사용자라면 상관없지만, 악의적인 공격자 입장에서는 이게 유의미한 정보가 된다. "아, 이 경로에 리소스가 있구나. 다른 방법으로 접근해보면 어떨까"라는 추가 시도로 이어질 수 있다.
비유하자면 이렇다. 바탕화면에 수상한 폴더가 보이는데 비밀번호가 걸려 있다. 뭔가 있다는 건 이미 알아버린 거다. 반면 처음부터 폴더가 숨겨져 보이지 않았다면, 거기에 무언가 있다는 사실 자체를 모른다. 서버 입장에서는 후자가 훨씬 안전하다.
리소스 존재 자체를 숨길 때 404가 더 의미 있다
바로 이 상황에서 403 대신 404를 선택하는 논리가 생긴다. 404 Not Found는 원래 "이 리소스가 존재하지 않는다"는 의미다. 하지만 서버가 접근 권한이 없는 사용자에게 404를 반환하면, 클라이언트는 리소스가 아예 없는 것으로 인식한다. 리소스의 존재 여부 자체가 노출되지 않는다.
이게 스펙을 어기는 게 아니냐는 반론이 나올 수 있다. 맞다, 스펙의 원래 의도와는 다르게 사용하는 것이다. 그런데 실무에서는 스펙의 정의보다 기능적 목적과 보안이 우선되는 경우가 있다. HTTP 상태 코드는 고정된 규칙이 아니라, 서버와 클라이언트 간 커뮤니케이션의 도구다. 어떤 도구를 어떻게 쓸지는 구현하려는 기능과 보호하려는 리소스의 성격에 따라 달라질 수 있다.
GitHub도 프라이빗 레포에 같은 전략을 쓴다
이 방식이 개인적인 판단이 아니라는 걸 보여주는 대표적인 사례가 GitHub다. 권한이 없는 사용자가 특정 프라이빗 레포지토리 URL에 접근하면, GitHub는 403을 반환하지 않고 404 페이지를 보여준다.
이 동작 방식에 의문을 가진 사람들이 GitHub 측에 꽤 많이 문의했던 모양이다. 그래서 GitHub는 공식 문서에 그 이유를 명확하게 설명해뒀다. 요지는 간단하다. 403을 반환하면 해당 프라이빗 레포지토리가 존재한다는 사실을 유추할 수 있기 때문에, 존재 여부 자체를 숨기기 위해 404를 반환한다는 것이다.
막상 보면 당연한 이야기 같은데, 이 결정이 공식 문서에 기록될 만큼 많은 사람들이 "버그 아닌가요?"라고 물어봤다는 게 흥미롭다. HTTP 상태 코드를 스펙 그대로만 이해하면, 이런 의도적인 설계가 오류처럼 보이는 것이다.
빈 결과를 404로 처리하면 안 되는 경우도 있다
반대 방향의 오류도 있다. 리소스가 존재하지 않는 게 아니라 단순히 결과가 비어 있는 상황에서 404를 반환하는 경우다.
예를 들어 어떤 채널의 구독자 목록을 요청했는데 구독자가 0명이라고 하자. 이때 서버가 404를 반환하면, 클라이언트는 400번대 에러로 처리하려 할 것이다. 그런데 클라이언트 입장에서 원하는 건 에러 처리가 아니라, 구독자가 있으면 목록을 보여주고 없으면 0 또는 빈 화면을 보여주는 것이다. 404를 받아서 에러 핸들러로 처리하게 되면 의도와 전혀 다른 흐름이 만들어진다.
이런 상황에서는 200과 함께 빈 배열을 반환하거나, 204 No Content를 반환해서 "요청은 성공했지만 돌려줄 내용이 없다"는 의미를 전달하는 게 적합하다. 리소스가 없는 것과 결과가 없는 것은 다르다. 이 차이를 구분하지 않으면 클라이언트와 서버 사이에 불필요한 오해가 생긴다.
상태 코드 선택이 실무에서 실제로 영향을 미치는 지점
결국 핵심은 하나다. HTTP 상태 코드를 외우는 것보다, 내가 구현하는 기능에 어떤 상태 코드가 더 적합한지 판단하는 능력이 중요하다.
403을 써야 할 상황인지, 404가 더 의도에 맞는 응답인지, 결과가 비어 있을 때 200으로 빈 배열을 줄지 204를 줄지 — 이런 결정들이 클라이언트 개발자와의 협업 품질을 바꾸고, 서비스의 보안 수준에도 영향을 미친다.
서버와 클라이언트가 상태 코드의 의미를 서로 다르게 이해하고 있으면 디버깅에 불필요한 시간이 들어가고, 응답 설계가 명확하지 않으면 예외 처리 코드가 엉키기 시작한다. 생각보다 이런 판단 하나가 나중에 꽤 큰 차이를 만든다.
스펙을 따르는 것도 중요하지만, 스펙이 만들어진 의도를 이해하고 상황에 맞게 적용하는 게 실무에서 한 단계 더 올라가는 방식이다.
댓글 17
로그인 회원가입