
웹 개발을 진행하다 보면 반드시 마주치게 되는 빨간색 경고 메시지가 있습니다. 바로 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).
'Artificial Intelligence > 60. Python' 카테고리의 다른 글
| [PYTHON] Gunicorn과 Uvicorn의 2가지 핵심 관계와 완벽 배포 설정 방법 (0) | 2026.03.20 |
|---|---|
| [PYTHON] API 속도 제한(Rate Limiting) 구현을 위한 3가지 알고리즘과 해결 방법 (0) | 2026.03.20 |
| [PYTHON] Django Signals 사용 시점과 3가지 회피 방법 및 성능 차이 분석 (0) | 2026.03.20 |
| [PYTHON] SQLAlchemy Session 관리 방법과 Scoped Session이 필요한 3가지 이유 (0) | 2026.03.20 |
| [PYTHON] ORM N+1 Problem 탐지를 위한 3가지 도구와 성능 해결 방법 (0) | 2026.03.20 |