배경
지금 개발하고 있는 사이트에서 데이터베이스 정보 변경이 필요해졌다.
기본에 클라이언트 측에서 준 데이터는 단순히 웹 사이트를 드래그해서 그대로 복붙한 내용이어서 원본 데이터가 표인 경우에 그 형태가 제대로 보존되지 않은 문제가 있었다.
그래서 xml 데이터를 다운로드 받아서 xml 내용이 보이도록 DB 구조를 변경하고 코드를 수정했는데 이제 xml 데이터를 하나하나 다 넣어주어야 한다는 번거로움이 생겼다!
클라이언트 측에서 원하는 데이터를 제공해주는 api도 없었기 때문에 정말 수동으로 데이터를 넣어주어야 하는 상황이 생긴것이다.
다행히 클라이언트 측에서 데이터를 알아서 넣어주신다고 하셔서 관리자페이지를 만들어서 db에 데이터를 넣을 수 있도록 하였다.
그래서 처음에는 xml -> json 파싱이 잘 되는지 파악하기 위해서 간단하게 백엔드에 html로 관리자페이지를 만들었었다.
그런데 UI가 너무 구려서 클라이언트분들이 접근하기에는 알맞지 않은 것 같아서 리액트로 분리해주었다.
그런데 여기서 갑자기 CORS오류가 엄청 생겨버렸다 😭
문제 상황
관리자 페이지를 폴더 경로 /backend/admin/ 안의 html로 제공할 때는 아무런 오류가 없었다.
그런데 동일한 기능을 React로 옮기고 프론트 개발서버에서 FastAPI 서버로 요청을 보내자 다음과 같은 오류가 발생하였다.
로그인 시도할 때
Access to fetch at 'https://api.medisafenurse.com/admin/login'
from origin 'http://localhost:5173'
has been blocked by CORS policy:
The 'Access-Control-Allow-Origin' header contains multiple values
'http://localhost:5173, *'...
xml 업로드 할 때
Request header field x-admin-token is not allowed by Access-Control-Allow-Headers
in preflight response.
문제 원인
Origin이 달라지면서 CORS 오류가 생긴 것 같다
백엔드에서 html을 만들 때는 html페이지와 api가 같은 도메인을 가지기 때문에 CORS 정책을 적용하지 않기 때문에 오류가 생기지 않는 것이다.
관리자페이지를 React로 옮겨주니까 React 개발 서버와 API 서버의 도메인이 달라지기 때문에 Cross Origin이 생긴 것!
브라우저가 외부 사이트의 요청이라고 판단하고 CORS 정책을 발동시켜서 계속 오류가 난 것이다.
지금 프로젝트는 Nginx를 이용해서 브라우저와 FastAPI를 연결시키고 있다.
그런데 FastAPI의 CORS 미들웨어랑 Nginx 헤더랑 요청이 중복되면서 계속 수정해주어도 CORS 오류가 발생하였던 것 같다.
브라우저는 둘 이상의 Origin 값이 들어있으면 보안상 불가능하다고 판단해 요청을 차단한다.
➡️ 그래서 계속 요청이 실패했던 것
해결 방법
1. Nginx의 CORS 헤더를 제거해주었다.
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods ...;
add_header Access-Control-Allow-Headers ...;
Nginx가 헤더를 만들지 않고 FastAPI에서만 CORS 헤더를 내보내도록 설정해주었다.
2. FastAPI CORS 설정에서 헤더 허용 목록에 X-Admin-Token을 추가해주었다.
- xml 업로드를 위해서 x-admin-token을 허용해주어야 했음.
app.add_middleware(
CORSMiddleware,
allow_origins=allow_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["Content-Type", "Authorization", "X-Admin-Token", "Origin"],
)
3) React 관리자 페이지에서 X-Admin-Token을 custom header로 전송해주었다.
const res = await fetch(`${API_BASE}/admin/upload-xml`, {
method: 'POST',
headers: { 'x-admin-token': token },
body: formData,
});
custom header는 무조건 Preflight 요청을 유도하므로 서버가 OPTIONS 요청을 처리할 준비가 되어 있어야 한다.
핵심 개념 정리
X-Admin-Token이란?
관리자 인증을 위해서 서버가 발급하는 임시 토큰
- 로그인 성공 시 FastAPI에서 발급
- 클라이언트에서 local state에 보관
- 이후 모든 관리자 API 요청에서 헤더로 전송
랜덤 Hex 토큰을 메모리에 저장하는 방식임
Preflight 요청이란?
브라우저가 위험할 수 있는 요청을 보내기 전에 서버에 먼저 OPTIONS 요청을 보내 안전한지 확인하는 것
다음 조건 중 하나라도 해당되면 Preflight 발동:
- POST/PUT/PATCH/DELETE 등
- custom header 포함 (예: X-Admin-Token)
- Content-Type이 JSON이 아닐 때(form-data 등)
결론
관리자페이지를 기존엔 백엔드 html에서 만들어서 origin이 같기 때문에 cors 오류가 나지 않은 것이었다.
리액트로 분리하였기 때문에 도메인이 달라져서 cors 오류가 났던 것!!
cors 헤더를 달았는데 오류가 난 근본적인 이유는 Nginx와 FastAPI 둘 다 Access-Control-Allow-Origin을 세팅해서 중복되어서 계속 오류가 났던 것이었다.
그래서 Nginx쪽 헤더는 제거해주고 FastAPI쪽만 남겨주었더니 제대로 동작하게 되었다.
⬇️ 만들어준 xml 업로드 관리자페이지!

'DevLog' 카테고리의 다른 글
| [BE devlog] FAERS 약물명 정규화 작업 (1) | 2026.03.23 |
|---|---|
| [BE devlog] FAERS 약물명 통합 쿼리 최적화 (0) | 2026.03.21 |
| [GitHub] GitHub 라벨 설정 한번에 등록하기 (0) | 2026.01.31 |
| AWS S3 + Presigned URL로 PDF 다운로드 구현하기 (0) | 2026.01.19 |