내가 shop.com에 들어갔을 때, 그 페이지의 자바스크립트가 https://bank.com/acc/balance 를 호출해 잔액 응답(JSON)을 그대로 읽을 수 있다면? 그러면 당연히 사용자가 정보 보호가 되지 않는다.
CORS는 “내가 로그인해 둔 사이트의 데이터(응답, DOM 등)가 다른 사이트의 자바스크립트에 의해 몰래 읽히지 않게” 막기 위한 정책이다. 예를 들어 shop.com에서 실행 중인 JS가 https://bank.com/acc/balance 를 fetch로 호출해도, 응답 내용을 읽는 건 기본적으로 차단된다.
이렇게 차단하는 정책은 서버가 응답에 넣어주는 CORS 헤더에 따라 브라우저가 행동하기 때문이다. 여기서 서버는 FastAPI, Nginx 설정 등을 말하며, 그 흐름은 다음과 같다.
즉, 사용자가 app.com 페이지에 있고, 거기서 JS가 fetch("https://api.com")을 하면 요청은 할 수 있다. 그런데 브라우저는 기본 규칙(SOP) 때문에 “다른 출처(api.com) 응답을 app.com의 JS가 읽는 건 원칙적으로 허용하지 않는다. 그래서 api.com 서버가 응답에 CORS 헤더(예: Access-Control-Allow-Origin: https://app.com)를 붙여서 "app.com에서 온 JS가 이 응답 읽어도 됨”이라고 예외 허가를 주면, res.json()이 동작하게 된다는 의미이다.
따라서 API 앱(FastAPI)에서 CORSMiddleware로 붙여도 되고, 앞단 프록시(Nginx)에서 add_header로 붙여도 된다. 둘 다 하면 충돌/중복 나기 쉬우니 한 군데에서만 관리하는 게 보통 깔끔하다. (특히 OPTIONS 프리플라이트 처리까지 포함해서)
$ sudo cat conf.d/cors_map.conf
# /etc/nginx/conf.d/cors_map.conf
# 허용할 Origin만 그대로 반환 (그 외/없음은 빈 문자열)
map $http_origin $cors_allow_origin {
default "";
"" "";
"https://aaaaa.kr" $http_origin;
"https://www.aaaaa.kr" $http_origin;
"http://localhost:1212" $http_origin;
"http://localhost:1313" $http_origin;
}
# Origin이 있고(=CORS 요청) 허용목록 아니면 1(차단), 나머지는 0(통과)
map $http_origin $cors_block {
default 1;
"" 0;
"https://aaaaa.kr" 0;
"https://www.aaaaa.kr" 0;
"http://localhost:1212" 0;
"http://localhost:1313" 0;
}
$ sudo cat conf.d/api.aaaaa.kr.conf
upstream aaaaa_api {
server 127.0.0.1:18000;
keepalive 64;
}
# -----------------------------------------------------------
# 80:
# -----------------------------------------------------------
server {
listen 80;
listen [::]:80;
server_name api.aaaaa.kr;
return 301 https://$host$request_uri;
}
# -----------------------------------------------------------
# 443:
# -----------------------------------------------------------
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name api.aaaaa.kr;
include /etc/nginx/snippets/ssl_cloudflare_origin.conf;
client_max_body_size 100M;
# 업스트림(FastAPI)이 CORS 헤더를 주면 중복 방지로 제거
proxy_hide_header Access-Control-Allow-Origin;
proxy_hide_header Access-Control-Allow-Credentials;
proxy_hide_header Access-Control-Allow-Methods;
proxy_hide_header Access-Control-Allow-Headers;
proxy_hide_header Access-Control-Max-Age;
proxy_hide_header Vary;
location / {
# 허용 Origin 아니면 차단
if ($cors_block) { return 403; }
# 프리플라이트(OPTIONS) 즉시 처리
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin $cors_allow_origin always;
add_header Access-Control-Allow-Credentials "true" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-CSRF-Token, X-Requested-With, X-Platform, X-Device-Id, X-Attestation-Token, X-API-KEY, Accept, Origin" always;
add_header Access-Control-Max-Age 600 always;
add_header Vary "Origin" always;
return 204;
}
# 일반 요청에도 CORS 헤더 부착
add_header Access-Control-Allow-Origin $cors_allow_origin always;
add_header Access-Control-Allow-Credentials "true" always;
add_header Vary "Origin" always;
proxy_pass http://aaaaa_api;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
location ~ /\.(?!well-known) {
deny all;
}
}
.
도메인 여러 개가 하나의 API를 쓴다면, 하나의 도메인으로 정규화(Canonical)하는 것이 유리히다. 예컨대, https://www.aaaaa.kr, https://aaaaa.kr, https://www.aaaaa.co.kr https://aaaaa.co.kr 4개가 있다면, CORS allowlist도 4개가 된다. 기술적으로는 4개를 허용하면 되지만, 하나로 리다이렉트하는 실무적으로 편리하다. 대안으로는 프론트를 aaaaa.kr로 고정하고, Nginx가 /api를 내부적으로 api.aaaaa.kr(또는 업스트림)으로 프록시하면 CORS 자체가 필요 없어진다.