저는 FastAPI를 사용하여 백엔드 API 서버를 구축하고 있었습니다. 데이터베이스 모델을 설계하고, 비즈니스 로직을 구현하고, 엔드포인트 별로 Pydantic 스키마를 정의하는 과정은 꽤나 순조로웠습니다. 구현된 기능 하나하나를 Swagger UI(Docs)와 Postman을 통해 테스트했고, 예상한 대로 완벽하게 JSON 데이터가 오고 가는 것을 확인했습니다.
제 로컬 환경의 Postman에서는 정상적으로 200 OK 응답을 받았으나, 크롬 개발자 도구의 콘솔 창은 붉은색 에러 메시지로 가득 차 있었습니다. 그리고 그 에러 로그 속에는 CORS 에러가 찍혀있었습니다.
Access to XMLHttpRequest at 'http://localhost:8000/api/items' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
사실 이 에러가 낯선 것은 아니었습니다. SpringBoot로 개발할 때도 종종 마주쳤던 에러였는데, 그때는 Config , Filtter Chain 안에서 어노테이션 하나 붙이거나 글로벌 설정 몇 줄이면 해결되곤 했던 일상적인 문제였습니다. 하지만 FastAPI 환경으로 넘어오면서 비즈니스 로직에만 몰두한 나머지, 웹 서버의 가장 기본적인 보안 정책 설정을 완전히 간과하고 있었던 것입니다.
CORS 설정
Postman에서는 되고 브라우저에서는 안 되는 이 상황은, 도구가 고장 난 것이 아니라 브라우저가 제 역할을 충실히 하고 있기 때문에 발생합니다. 이를 이해하기 위해서는 웹 브라우저의 보안 정책인 동일 출처 정책(SOP, Same-Origin Policy)을 다시 한번 기억할 필요가 있습니다.
여기서 출처(Origin)란 프로토콜(http, https), 호스트(도메인), 포트 번호의 조합을 의미합니다. 만약 이 셋 중 하나라도 다르면 브라우저는 서로 다른 출처로 인식합니다.
당시 우리의 개발 환경을 보면, 프론트엔드 React 애플리케이션은 http://localhost:3000에서 실행되고 있었고, 저의 FastAPI 서버는 http://localhost:8000에서 실행되고 있었습니다. 도메인(localhost)과 프로토콜(http)은 같았지만, 포트 번호가 달랐기 때문에 브라우저 입장에서 이 둘은 엄연히 타인이었습니다.
브라우저는 기본적으로 보안을 위해 매우 보수적인 입장을 취합니다. 사용자가 악의적인 사이트에 접속했을 때, 그 사이트의 스크립트가 사용자가 로그인한 다른 정상적인 사이트에 몰래 요청을 보내 정보를 탈취하는 것을 막아야 하기 때문입니다. 그래서 브라우저는 다른 출처로 향하는 자바스크립트의 요청을 원천적으로 차단하는 것을 기본 동작으로 삼습니다.
SpringBoot에서는 습관처럼 처리하던 것이라 잊고 있었지만, FastAPI 역시 모던 웹 프레임워크로서 이러한 보안 규칙을 철저히 준수해야 했습니다. 아래 다이어그램은 당시 제 상황을 시각적으로 보여줍니다.

결국 문제는 서버가 브라우저에게 "이 출처(localhost:3000)는 안전하니 데이터를 줘도 된다"라는 허가증을 발급해주지 않아서 발생한 것이었습니다. 이 허가증이 바로 Access-Control-Allow-Origin 헤더입니다.
FastAPI는 이러한 CORS 처리를 위해 매우 직관적인 미들웨어(Middleware)를 제공합니다.
(미들웨어에 대한 설명은 다른 장에서... )
SpringBoot에서 WebMvcConfigurer를 구현하거나 @CrossOrigin을 사용하는 것과 유사합니다.
FastAPI의 CORSMiddleware를 사용하면 단 몇 줄의 코드로 이 문제를 해결할 수 있습니다.
당시 저는 당장의 개발 진행을 위해 가장 관대한 설정으로 문제를 해결했습니다.
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# CORS 설정을 위한 미들웨어 추가
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 모든 출처에서의 접근을 허용합니다. (개발 단계용)
allow_credentials=True,
allow_methods=["*"], # 모든 HTTP 메서드(GET, POST, PUT 등)를 허용합니다.
allow_headers=["*"], # 모든 HTTP 헤더를 허용합니다.
)
@app.get("/")
async def root():
return {"message": "CORS 설정이 적용되었습니다."}
위의 코드를 메인 애플리케이션 파일에 추가하고 서버를 재시작했습니다.
allow_origins=["*"] 설정은 모든 출처에서의 요청을 허용하겠다는 의미입니다. 덕분에 급한 불은 껐고 프론트엔드와의 연동 작업은 다시 활기를 되찾았습니다.
하지만 SpringBoot 에서 돌이켜보면, 실무 환경에서 이렇게 와일드카드(*)를 남발하는 것은 보안상 좋지 않다는 것을 알고 있었습니다. 로컬 개발 환경에서는 편리하지만, 실제 서비스가 운영되는 프로덕션 환경에서는 구체적인 도메인을 명시해야 합니다.
또한, 단순히 API를 호출하는 것을 넘어 로그인 인증 정보(Cookie, Authorization Header)를 주고받아야 하는 상황이 오면, 이 와일드카드 설정만으로는 해결되지 않는 또 다른 문제에 직면하게 됩니다.
이어지는 다음 2부에서는 이번에 적용한 기초적인 설정을 넘어서, 인증 정보(Credential)가 포함된 요청을 처리할 때 발생하는 이슈와, 보안을 고려한 정교한 CORS 설정 방법에 대해 자세히 다루어 보겠습니다.
'ServerDev > FastAPI' 카테고리의 다른 글
| [FastAPI - CORS 마스터 3부] 개발(Local) vs 운영(Prod) 환경 분리와 실전 트러블 슈팅 (0) | 2025.12.12 |
|---|---|
| [FastAPI - CORS 마스터 2부] 쿠키 인증(Credential) 이슈 해결과 보안을 위한 정교한 허용 전략 (0) | 2025.12.11 |
| [FastAPI] 웹 프레임워크가 아니라 'AI 모델 서빙기'로 사용하기 (feat. Spring Boot) (0) | 2025.12.09 |
| [FastAPI - DI 마스터 3부] 의존성 오버라이드(Override)와 고급 패턴 (0) | 2025.12.09 |
| [FastAPI - DI 마스터 2부] Depends 사용법 기초부터 yield를 이용한 DB 연동까지 실전 정복 (0) | 2025.12.06 |