한양과학기술고등학교

에듀테크 프로젝트 대시보드

CORS (교차 출처 리소스 공유)

CORS — "다른 출처인데, 이 응답 읽어도 돼?"

브라우저는 기본적으로 같은 출처(origin)의 응답만 자바스크립트가 읽게 해준다(동일 출처 정책·SOP). CORS는 서버가 특정 헤더로 "이 출처는 허용해"라고 명시적으로 허락해 주는 규칙이다. 서버가 아니라 브라우저가 강제한다는 점이 핵심.

한 줄 요약: 요청은 대부분 서버까지 도달한다. 다만 CORS 헤더가 없으면 브라우저가 응답을 JS에게 안 넘겨준다. 그리고 위험한 요청(사용자 정의 헤더, PUT/DELETE 등)은 먼저 OPTIONS 프리플라이트로 허락을 받아야 한다.

1. "출처(origin)"란 정확히 무엇인가

출처 = 스킴 + 호스트 + 포트 세 가지의 조합. 하나라도 다르면 "교차 출처(cross-origin)"다. 경로(path)·쿼리·프래그먼트는 출처와 무관하다.

https :// shop.example.com : 443 /api/cart?id=7
기준 URL: https://shop.example.com/page같은 출처?이유
https://shop.example.com/other/pathO경로만 다름 — 출처는 동일
http://shop.example.com/pageX스킴 다름 (https ≠ http)
https://api.example.com/pageX호스트 다름 (서브도메인도 별개)
https://shop.example.com:8443/pageX포트 다름

2. 직접 해보기 — 요청을 만들고 브라우저 판정 보기

프런트엔드는 https://app.mysite.com 에서 실행 중이라고 하자. 아래에서 요청 조건과 서버가 내려주는 CORS 헤더를 바꿔 가며, 브라우저가 응답을 넘겨줄지 막을지 확인해 보자.

조건을 고르고 요청 보내기를 눌러보세요.

3. "단순 요청" vs "프리플라이트 필요"

브라우저는 요청이 단순 요청(simple request) 조건을 모두 만족하면 바로 보낸다. 하나라도 어기면 먼저 OPTIONS 프리플라이트를 보내 서버에 허락을 구한다.

조건단순 요청 SIMPLE프리플라이트 PREFLIGHT
메서드GET, HEAD, POSTPUT, DELETE, PATCH ...
Content-Typetext/plain · form-urlencoded · multipart/form-dataapplication/json 등
사용자 정의 헤더없음 (기본 헤더만)X-Auth-Token 등 추가
왜 프리플라이트가 있나? 옛날 서버들은 CORS를 몰랐다. 브라우저가 갑자기 DELETE 를 던지면 서버가 실제로 데이터를 지워버릴 수 있다. 그래서 "부작용이 있을 법한" 요청은 먼저 OPTIONS로 물어보고, 서버가 명시적으로 "그 메서드·그 헤더 허용해"라고 답할 때만 본 요청을 보낸다.

4. 프리플라이트 왕복 예시

# 1) 브라우저가 자동으로 보내는 프리플라이트
OPTIONS /data HTTP/1.1
Host: api.mysite.com
Origin: https://app.mysite.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: x-auth-token, content-type

# 2) 서버 응답 — "그 조건이면 허용"
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.mysite.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: X-Auth-Token, Content-Type
Access-Control-Max-Age: 600   # 10분간 프리플라이트 캐시

# 3) 이제서야 실제 PUT 요청이 나간다
PUT /data HTTP/1.1
Origin: https://app.mysite.com
X-Auth-Token: abc123
Content-Type: application/json

5. 서버 쪽 설정 (FastAPI 예시)

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()
app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://app.mysite.com"],  # "*" 는 credentials 와 함께 못 씀
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["X-Auth-Token", "Content-Type"],
    max_age=600,
)
보안 주의: "요청 Origin을 그대로 반사 + Allow-Credentials: true" 는 사실상 아무 사이트나 쿠키를 실어 요청하게 허용하는 구멍이다. 허용 목록(allowlist)을 명시하자. Access-Control-Allow-Origin: * 와 credentials 는 동시에 쓸 수 없다(브라우저가 거부).

6. 자주 하는 오해