Python에서는 API를 읽을 수 있는데 브라우저 JavaScript에서는 실패한다면 CORS를 의심할 수 있습니다. 하지만 Console에 CORS 메시지가 보인다는 사실만으로 원인이 확정되지는 않습니다. 로그인 이동, 서버 오류, 사전 요청 실패를 Network 기록에서 나눠 확인해야 합니다.
먼저 두 주소의 출처를 비교하기
웹 출처는 스킴·호스트·포트의 조합입니다. 경로만 다르면 같은 출처일 수 있지만 http://localhost:3000과 http://localhost:8000은 포트가 달라 교차 출처입니다. localhost와 127.0.0.1도 같은 컴퓨터를 가리키더라도 호스트가 다릅니다. 동일 출처 정책
현재 win-j 글쓰기 화면의 같은 출처 경로로 보내는 요청에는 별도의 CORS 허용 설정이 필요하지 않습니다. 프로젝트의 기본 미들웨어에도 django-cors-headers가 등록되어 있지 않습니다. 개발 프런트엔드를 별도 포트로 분리한다면 새로운 교차 출처 구성이 되므로 그때 요청·인증 구조를 다시 설계해야 합니다.
단순 요청과 사전 요청의 차이
일부 교차 출처 요청은 바로 전송되고, 브라우저가 응답의 CORS 헤더를 확인한 뒤 JavaScript에 결과를 공개할지 결정합니다. JSON Content-Type이나 Authorization 같은 헤더를 사용하는 요청은 일반적으로 먼저 OPTIONS 사전 요청을 보냅니다. OPTIONS가 허용되지 않으면 실제 요청이 전송되지 않을 수 있습니다. MDN CORS 설명
| Network에서 보이는 상황 | 해석할 단서 |
|---|---|
| OPTIONS만 있고 실패 | 메서드·헤더·출처 허용, 인증 또는 이동 응답 확인 |
| GET이 200인데 JS에서 못 읽음 | 응답의 출처 허용 헤더와 자격 증명 조건 확인 |
| 302 뒤 로그인 HTML | 인증 실패가 먼저인지 확인 |
| POST가 403이고 CSRF 안내 | 서버의 요청 위조 방지 검사 확인 |
| 502·504와 CORS 메시지가 함께 보임 | 프록시·서버 장애로 정상 헤더가 누락됐는지 확인 |
POST라고 항상 preflight가 있는 것은 아닙니다. 특정 조건의 폼 POST는 바로 전송될 수 있습니다. 그러므로 CORS만으로 다른 사이트가 보내는 모든 상태 변경 요청을 차단한다고 가정하면 안 됩니다.
헤더 한 줄을 추가하는 것으로 끝나지 않는 이유
브라우저가 Origin: https://app.example.com으로 요청한 경우 서버가 그 출처를 허용할지 정책으로 결정해야 합니다. 허용된 출처를 동적으로 응답한다면 캐시가 출처별 응답을 혼동하지 않도록 Vary: Origin도 고려합니다.
쿠키 같은 자격 증명을 포함하는 교차 출처 요청에서는 Access-Control-Allow-Origin: *를 사용할 수 없으며 구체적인 허용 출처와 자격 증명 허용 설정이 필요합니다. 반면 인증이 필요 없는 공개 데이터에 와일드카드를 쓰는 것은 정책상 가능한 선택입니다. 모든 출처를 무조건 반사하거나 제한된 API에 와일드카드를 붙이지 않습니다.
Django에서 이름이 비슷한 설정 구분
| 설정·장치 | 실제 역할 | 대신하지 않는 것 |
|---|---|---|
| ALLOWED_HOSTS | 서버가 받을 Host 검증 | CORS 응답 허용 |
| CSRFTRUSTEDORIGINS | CSRF 검사에서 신뢰할 출처 지정 | 토큰 검사 전체 면제, CORS 허용 |
| CSRF 토큰·Origin 검사 | 원치 않는 상태 변경 요청 방어 | 사용자 권한 검사 |
| CORS 응답 헤더 | 브라우저에 응답 읽기 허용 정책 전달 | 서버 인증·권한·CSRF 보호 |
현재 관리자 저장은 세션과 CSRF 보호를 사용합니다. CSRF의 신뢰 출처를 추가해도 필요한 토큰이 사라지는 것은 아닙니다. 기존 MIDDLEWARE 목록을 예제 한 줄로 덮어쓰거나 CSRF 보호를 끄는 방식으로 오류를 해결하지 않습니다. Django CSRF 검사
점검 기록을 남기는 방법
출발 페이지의 출처, 대상 URL, 메서드, Content-Type, 추가 헤더 이름, OPTIONS 상태, 실제 응답 상태를 기록합니다. 쿠키·토큰 값은 제외합니다. 서버 로그에 실제 POST가 있는지 확인하면 “전송이 막혔는지”와 “응답을 못 읽는지”를 구분하는 데 도움이 됩니다.
fetch(..., {mode: "no-cors"})로 바꾸면 교차 출처 응답이 opaque가 되어 본문과 상태를 원하는 방식으로 읽을 수 없습니다. 브라우저의 보호 기능을 끄는 확장 기능도 배포할 해결책이 아닙니다. Fetch의 요청 모드
Python Requests와 Django 테스트 클라이언트는 브라우저의 CORS 정책을 직접 집행하지 않습니다. 이들로 HTTP 상태·헤더를 확인한 뒤 실제 브라우저에서 교차 출처 동작을 확인해야 합니다. 인증 문제가 먼저 보이면 쿠키·세션 점검, 형식이 다르면 HTTP 헤더 점검으로 이어갑니다.
첫 댓글을 남겨보세요.