CORS의 개념과 동작 원리 Simple Request와 Preflight
시작하기에 앞서
예전에 CORS 글을 한 번 써둔 적이 있는데, 그때는 “대충 이런 느낌” 정도로만 정리했었다. 이번에는 브라우저가 어떤 기준으로 CORS를 적용하고, 왜 어떤 요청은 바로 보내고 어떤 요청은 OPTIONS를 먼저 보내는지 흐름을 기준으로 정리하려 한다.
CORS란?
CORS(Cross-Origin Resource Sharing)는 브라우저가 교차 출처 요청을 다룰 때 적용하는 보안 규칙이다.
- 브라우저는 보안을 위해 같은 출처(Same-Origin) 에서만 리소스를 주고받게 제한한다.
- 같은 출처는 프로토콜 + 호스트(도메인) + 포트 조합이 모두 같을 때를 말한다.
- 셋 중 하나라도 다르면 다른 출처(Cross-Origin) 로 취급한다.

https://myapp.vercel.app→https://api.myapp.vercel.app(호스트가 달라서 Cross-Origin)http://myapp.vercel.app→https://myapp.vercel.app(프로토콜이 달라서 Cross-Origin)https://localhost:3000→https://localhost:4000(포트가 달라서 Cross-Origin)
브라우저는 어떻게 CORS를 판단할까?
브라우저는 요청을 보낼 때 “이게 교차 출처 요청인지”를 다음처럼 판단한다.
-
현재 페이지의 Origin을 기준으로 잡는다.
예:https://myapp.vercel.app -
fetch,axios등으로 요청할 대상 URL의 Origin을 뽑는다.
예:https://api.myapp.vercel.app -
두 Origin이 다르면 CORS 정책을 적용한다.
-
CORS 요청이면 브라우저가 자동으로
Origin헤더를 붙이고, 서버 응답의 CORS 관련 헤더를 검사해서 JS 코드가 응답을 읽어도 되는지 결정한다.
한 줄로 줄이면 이거다.
브라우저는 “요청을 보낸 쪽 Origin”과 “요청 대상 Origin”이 다르면 CORS 정책을 적용한다.
CORS의 핵심 포인트
CORS에서 헷갈리기 쉬운 포인트가 하나 있다.
- 요청 자체를 막는 게 아니다.
- 서버 응답은 도착할 수 있다.
- 다만 브라우저가 JS에서 응답을 읽는 것을 차단한다.
그래서 네트워크 탭에는 응답이 보이는데, 콘솔에는 CORS 에러가 뜨는 상황이 나온다.
CORS 동작 흐름
CORS는 크게 두 방식으로 동작한다.
- 단순 요청(Simple Request)
- 사전 요청(Preflight Request)
단순 요청 (Simple Request)
아래 조건을 만족하면 브라우저는 바로 본 요청을 보낸다.
- 메서드가
GET/POST/HEAD중 하나 - 요청 헤더가 “단순 헤더(simple header)” 범위 안에 있다
(예:Accept,Content-Type등. 단,Content-Type은 아래 값일 때만 단순 요청 조건에 들어간다) Content-Type이 아래 중 하나다application/x-www-form-urlencodedmultipart/form-datatext/plain
동작 흐름
- 브라우저 → 서버 (본 요청 전송)
GET /data
Origin: https://myapp.vercel.appOrigin은 “이 요청이 어디서 왔는지”를 나타낸다.- 보안상 이유로
프로토콜 + 호스트 + 포트까지만 포함하고, 경로(path)는 포함하지 않는다. - 사용자가 임의로
Origin값을 넣어서 조작하는 건 브라우저 환경에서 막혀 있다.
- 서버 → 브라우저 (CORS 허용 여부를 헤더로 답한다)
Access-Control-Allow-Origin: https://myapp.vercel.app브라우저는 응답을 받으면 이런 식으로 검사한다.
Access-Control-Allow-Origin값이 요청의Origin과 일치하는가?- 쿠키/인증정보를 포함하는 요청이면,
Access-Control-Allow-Credentials: true가 있는가?
조건이 맞으면 브라우저가 JS 코드에 응답을 넘겨준다.
- 서버가 허용하지 않으면
- 서버가
Access-Control-Allow-Origin을 안 주거나, 값이 매칭되지 않으면 - 브라우저 콘솔에 CORS 에러가 뜨고, JS 코드에서 응답 접근이 막힌다.
사전 요청 (Preflight Request)
요청이 단순 요청 조건을 만족하지 않으면, 브라우저는 본 요청 전에 OPTIONS로 허락을 먼저 구한다.
- 메서드가
PUT,PATCH,DELETE같은 것들일 때 Authorization같은 커스텀 헤더를 붙일 때Content-Type: application/json처럼 단순 요청 조건을 벗어날 때
동작 흐름
- 브라우저 → 서버 (사전 요청)
OPTIONS /user
Origin: https://myapp.vercel.app
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization“내가 이 Origin에서 PUT 메서드랑 이런 헤더로 요청 보내려고 하는데 괜찮나?”
- 서버 → 브라우저 (허용 정책 응답)
Access-Control-Allow-Origin: https://myapp.vercel.app
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 3600Access-Control-Allow-Methods: 허용할 메서드Access-Control-Allow-Headers: 허용할 헤더Access-Control-Max-Age: 같은 조건의 preflight를 일정 시간 캐시해서 OPTIONS를 매번 보내지 않게 한다
- 브라우저 → 서버 (본 요청 전송)
preflight가 통과하면 그제서야 실제 요청이 나간다.
PUT /user
Origin: https://myapp.vercel.app
Content-Type: application/json
Authorization: Bearer token...정리
- CORS는 브라우저가 교차 출처 요청을 다룰 때 적용하는 보안 규칙이다.
- 브라우저는 요청의 Origin과 대상 Origin을 비교해 다르면 CORS를 적용한다.
- 단순 요청이면 바로 본 요청을 보내고,
- 단순 요청이 아니면 OPTIONS(preflight)로 먼저 허락을 구한 뒤 본 요청을 보낸다.
- CORS 위반 시 서버 응답이 도착해도 브라우저가 JS 접근을 차단한다.