본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] CORS 에러가 발생하는 3가지 근본 원인과 파이썬 백엔드 해결 방법

by Papa Martino V 2026. 3. 20.
728x90

CORS(Cross-Origin Resource Sharing)
CORS (Cross-Origin Resource Sharing)

 

웹 개발을 진행하다 보면 반드시 마주치게 되는 빨간색 경고 메시지가 있습니다. 바로 CORS(Cross-Origin Resource Sharing) 에러입니다. 프론트엔드에서 API를 호출했을 때 브라우저 콘솔에 나타나는 이 에러는 보안상의 이유로 발생하지만, 초보 개발자부터 숙련된 엔지니어까지 당혹스럽게 만들곤 합니다. 오늘 이 글에서는 CORS의 메커니즘을 심층적으로 분석하고, 파이썬 기반의 프레임워크(FastAPI, Flask, Django)에서 이를 완벽하게 제어하는 기술적 해결 방법을 제시합니다.


1. CORS 에러의 정체: 브라우저의 파수꾼, SOP

CORS 에러를 이해하기 위해서는 먼저 SOP(Same-Origin Policy, 동일 출처 정책)를 알아야 합니다. 브라우저는 보안을 위해 동일한 출처(도메인, 프로토콜, 포트)에서 온 리소스만 상호작용할 수 있도록 제한합니다. CORS는 이러한 엄격한 제한을 안전하게 해제하여 다른 출처의 자원을 사용할 수 있도록 허용해주는 '예외 조항'입니다.

CORS가 판단하는 'Origin'의 구성 요소

두 URL이 동일한 출처인지 판단하는 기준은 다음 3가지가 모두 일치해야 합니다.

  • Protocol: http와 https는 서로 다른 출처입니다.
  • Host: domain.com과 api.domain.com은 서로 다른 출처입니다.
  • Port: 80포트와 8080포트는 서로 다른 출처입니다.

2. CORS의 작동 방식과 2가지 요청 유형 차이

브라우저는 외부 리소스를 요청할 때 서버에 미리 '본 요청을 보내도 괜찮은지' 물어보는 과정을 거치기도 합니다. 이를 이해하는 것이 디버깅의 핵심입니다.

구분 단순 요청 (Simple Request) 예비 요청 (Preflight Request)
방식 본 요청을 바로 전송 OPTIONS 메서드로 사전 확인 후 본 요청 전송
조건 GET, POST, HEAD 메서드 / 커스텀 헤더 없음 PUT, DELETE 사용 시 / Authorization 헤더 포함 시
특징 조건이 매우 까다로워 현대 API에서는 드묾 대부분의 REST API가 이 방식을 따름
실패 원인 서버 응답 헤더에 Allow 정보 누락 OPTIONS 요청에 대한 서버의 거부

3. 파이썬 프레임워크별 CORS 해결 방법

파이썬 백엔드 생태계에서 가장 많이 쓰이는 3대 프레임워크의 설정법을 정리했습니다. 핵심은 서버가 Access-Control-Allow-Origin 헤더를 응답에 포함하게 만드는 것입니다.

(1) FastAPI: CORSMiddleware 활용

FastAPI는 Starlette의 미들웨어를 사용하여 매우 직관적으로 설정할 수 있습니다.

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

origins = [
    "http://localhost:3000",
    "https://your-frontend-domain.com",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

(2) Flask: Flask-CORS 라이브러리 활용

Flask는 외부 확장을 통해 가장 간단하게 전역 혹은 특정 라우트에 CORS를 적용할 수 있습니다.

from flask import Flask
from flask_cors import CORS

app = Flask(__name__)
# 전역 설정
CORS(app, resources={r"/api/*": {"origins": "http://localhost:3000"}})

(3) Django: django-cors-headers 미들웨어

Django는 설정 파일(settings.py)을 통해 체계적으로 관리합니다.

# settings.py
INSTALLED_APPS = [
    ...,
    'corsheaders',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',  # 가장 상단에 위치 권장
    'django.middleware.common.CommonMiddleware',
    ...,
]

CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000",
]

4. [Sample Example] 실무에서 흔히 저지르는 실수와 해결책

CORS 설정을 마쳤음에도 불구하고 여전히 에러가 발생하는 특수 상황에 대한 시나리오입니다.

문제: "분명히 allow_origins=["*"]로 설정했는데 인증 정보(Cookie/Authorization)를 담아 보낼 때 에러가 납니다."

해결 방법: allow_credentials=True를 설정할 경우, allow_origins는 와일드카드(*)를 사용할 수 없습니다. 반드시 신뢰할 수 있는 구체적인 도메인 리스트를 명시해야 합니다.


5. 전문적인 백엔드 아키텍트의 보안 조언

CORS 해결이 단순히 '에러를 없애는 것'에 그쳐서는 안 됩니다. 보안과 타협하지 않는 최적의 설계 방안을 제언합니다.

  • 화이트리스트 운영: 편리함을 위해 "*"를 남발하지 마세요. 서비스가 운영되는 실제 도메인만 허용하는 것이 CSRF 공격으로부터 서비스를 보호하는 첫걸음입니다.
  • 인프라 계층 처리: API 서버 코드에 직접 작성하기보다 Nginx, AWS CloudFront, API Gateway 등 인프라 앞단에서 CORS 헤더를 일괄 처리하는 것이 성능과 관리 측면에서 유리할 수 있습니다.
  • Preflight 캐싱: Access-Control-Max-Age를 설정하여 불필요한 OPTIONS 요청이 반복되지 않도록 브라우저 캐싱을 활용하세요.

6. 내용의 출처 및 참고 문헌

  • MDN Web Docs: "Cross-Origin Resource Sharing (CORS)" - https://developer.mozilla.org/
  • FastAPI Documentation: "CORS (Cross-Origin Resource Sharing)" official guide.
  • Django-cors-headers GitHub Repository Documentation.
  • W3C Specification: "Cross-Origin Resource Sharing" (Recommendation).
728x90