Python FastAPI CORS 차단 에러: 미들웨어(Middleware) 실행 순서 완벽 제어
[Troubleshooting] Python FastAPI CORS 차단 에러: 미들웨어 실행 순서 완벽 제어
"FastAPI 공식 문서에 나온 대로 CORSMiddleware를 정확히 추가하고 허용 도메인(origins) 리스트도 완벽하게 설정했습니다. 그런데 React 프론트엔드에서 API를 호출하기만 하면 자꾸 브라우저가 CORS 정책 위반이라며 통신을 강제로 끊어버립니다."
빠르고 직관적인 파이썬 기반 비동기 프레임워크인 FastAPI를 도입한 백엔드 엔지니어들이 며칠 밤을 새우게 만드는 미스터리한 병목입니다. 설정 코드는 100% 완벽한데 브라우저는 끈질기게 에러를 뿜어냅니다.
초보자들은 허용 도메인에 와일드카드(*)를 넣었다 뺐다 하며 삽질을 반복합니다. 하지만 이 문제의 본질은 도메인 주소가 아니라 '미들웨어 스택(Middleware Stack)이 쌓이는 순서'에 숨어 있습니다. 본 가이드에서는 FastAPI 내부의 요청 처리 파이프라인을 해체하고, 인증(Auth) 미들웨어와 CORS 미들웨어 간의 충돌을 완벽히 해결하는 아키텍처를 단호하게 제시합니다.
📌 이 글의 핵심 포인트
- 미들웨어 양파 구조: 먼저 등록(add)한 미들웨어가 가장 나중에 실행되는 Starlette(FastAPI 기반) 프레임워크의 역순 아키텍처
- Preflight의 절망: 브라우저의 OPTIONS 핑(Ping) 테스트가 인증(Auth) 미들웨어에 막혀 CORS 헤더를 받지 못하는 현상
- 순서의 미학: CORSMiddleware를 파이프라인의 가장 바깥쪽에 씌워 모든 에러를 방어하는 무결점 설정법
1단계: 핵심 원인 분석 - 인증(Auth) 미들웨어의 팀킬
FastAPI는 양파 껍질처럼 요청(Request)을 처리합니다. 클라이언트의 요청이 들어오면 미들웨어들을 하나씩 거치며 코어 라우터로 진입합니다. 그런데 브라우저가 쏘는 CORS 사전 요청인 OPTIONS 패킷에는 당연히 유저의 인증 토큰(JWT)이 들어있지 않습니다.
만약 당신의 서버가 CORS 처리보다 '인증 확인'을 먼저 하도록 세팅되어 있다면 어떻게 될까요? OPTIONS 요청이 들어오자마자 인증 미들웨어가 "어? 너 토큰 없네?"라며 401 Unauthorized 에러를 던지고 문을 닫아버립니다. 결국 CORS 미들웨어는 OPTIONS 패킷을 구경조차 못 하게 되고, 브라우저에는 접근 허용 헤더(Allow-Origin)가 전달되지 않아 CORS 에러로 둔갑하는 것입니다.
2단계: 완벽한 트러블슈팅 - 미들웨어 주입 순서 역전
FastAPI(엄밀히 말하면 그 기반인 Starlette)의 add_middleware는 코드가 가장 나중에 적힌 미들웨어가 가장 바깥쪽(제일 먼저 실행됨)에 위치하는 특징이 있습니다. LIFO(Last In, First Out) 구조입니다.
따라서 CORSMiddleware는 다른 모든 미들웨어가 추가된 후, 코드의 제일 마지막 줄에 적어주어야 가장 먼저 실행되어 OPTIONS 요청을 무사히 통과시킵니다.
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from myapp.middleware import CustomAuthMiddleware
app = FastAPI()
# ❌ 최악의 안티 패턴 (CORS 에러 발생)
# CORSMiddleware를 먼저 적어버리면, 실제로는 제일 안쪽에 위치하게 되어
# 인증 미들웨어가 OPTIONS 요청을 먼저 차단해버립니다.
# app.add_middleware(CORSMiddleware, ...)
# app.add_middleware(CustomAuthMiddleware)
# ✅ 완벽한 아키텍처 (CORS 통과 보장)
# 인증 등 커스텀 미들웨어를 먼저 쌓아둡니다.
app.add_middleware(CustomAuthMiddleware)
# 제일 마지막 줄에 CORSMiddleware를 추가하여 양파의 가장 바깥쪽 껍질로 만듭니다.
# 이제 브라우저의 OPTIONS 요청은 인증 검사를 받기 전에 CORS 허락을 먼저 받습니다.
app.add_middleware(
CORSMiddleware,
allow_origins=["https://www.my-frontend.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
3단계: 동적 허용 도메인 관리 (Security Warning)
CORS 설정에서 allow_credentials=True (쿠키 및 토큰 공유 허용) 옵션을 켰다면, allow_origins에 ["*"] 와일드카드를 절대 사용할 수 없습니다. 프레임워크가 시작할 때 에러를 뱉습니다. 테스트 환경이 아닌 프로덕션(운영) 서버라면 반드시 프론트엔드의 정확한 도메인 주소(https 포함, 끝에 슬래시(/) 제외)를 명시하여 엔터프라이즈급 보안을 유지해야 합니다.
🙋♂️ 자주 묻는 질문 (FAQ)
Q. Nginx 같은 앞단 리버스 프록시에서도 CORS 설정을 했는데 충돌이 날까요?
A. 네, Nginx에서 add_header Access-Control-Allow-Origin을 세팅하고 FastAPI에서도 똑같이 셋업하면 헤더가 두 번 겹쳐서 브라우저가 또다시 에러를 냅니다. CORS 제어는 반드시 Nginx(인프라)나 FastAPI(애플리케이션) 둘 중 한 곳에서만 처리해야 합니다. 보통은 Nginx 단에서 처리하는 것이 성능상 유리합니다.
💡 핵심 정리 및 마무리
프레임워크의 코드를 복사해서 붙여넣는 것만으로는 충분하지 않습니다. 프레임워크가 내부적으로 데이터를 어떤 순서(Lifecycle)로 낚아채고 가공하는지, 그 추상화된 파이프라인의 구조를 꿰뚫어 보아야 합니다. 미들웨어의 실행 순서를 지배하여 불필요한 네트워크 차단벽을 거둬내고 막힘없는 풀스택 API 통신을 구현하시기 바랍니다.
댓글
댓글 쓰기