
Cross-Origin Resource Sharing (CORS) - HTTP | MDN
Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that allows a server to indicate any origins (domain, scheme, or port) other than its own from which a browser should permit loading resources. CORS also relies on a mechanism by which browsers make a "preflight" request to the server hosting the cross-origin resource, in order to check that the server will permit the actual request. In that preflight, the browser sends headers that indicate the HTTP method and headers that will be used in the actual request.
CORS (Cross-Origin Resource Sharing, 교차 출처 리소스 공유)
→ "다른 출처(도메인)의 리소스를 안전하게 사용하기 위한 규칙(정책)"임
1. CORS는 왜 필요한가? - '동일 출처 정책' 때문
은행 앱을 예로 들면
- 동일 출처 정책 (Same-Origin Policy, SOP): 웹 브라우저의 매우 중요한 보안 규칙
"A은행 사이트(
https://a-bank.com)에서 실행된 스크립트는 A은행 서버의 데이터만 요청할 수 있다"는 원칙. 만약 이 규칙이 없다면, 악성 사이트(https://evil-site.com)에 접속했을 때 그 사이트의 스크립트가 멋대로 A은행 서버에 요청을 보내 계좌 정보를 훔쳐갈 수 있음- 문제의 시작: 하지만 현대 웹은 여러 출처의 리소스를 조합해 만드는 경우가 많음.
예를 들어, 내 쇼핑몰(
https://my-shop.com)에서 카카오 지도 API(https://dapi.kakao.com)를 가져와 지도를 보여주고 싶은데. 이때 '동일 출처 정책'에 따르면 내 쇼핑몰은 카카오의 리소스를 요청할 수 없어야 함- CORS의 역할: 이 문제를 해결하기 위해 CORS가 등장했음.
CORS는 서로 다른 출처 간에도 특정 조건 하에서는 리소스 공유를 허용해주는 '안전한 예외 규칙'
즉, 카카오 서버가 "
https://my-shop.com은 내 지도 데이터를 가져가도 괜찮아"라고 허락해주면 브라우저가 이를 확인하고 데이터를 정상적으로 보여주는 방식2. CORS 동작 과정 - HTTP 헤더를 이용한 대화
CORS의 핵심은 브라우저와 서버가 HTTP 헤더를 통해 서로 대화하며 허락을 구하고 받는 과정임
마치 아파트에 택배 보내는 과정과 같음
- 나 (브라우저): 다른 동네(Origin)에 사는 친구에게 택배를 보내려고 함
- 택배 (HTTP 요청):
GET,POST,PUT등
- 친구네 아파트 경비실 (서버): 보안을 담당
만약 내가 보내는 것이 간단한 편지(
GET 요청 등)라면, 경비실은 그냥 통과시켜 줌. 이걸 "단순 요청(Simple Request)"이라고 함하지만 내가 커다란 소포(
PUT 요청, 커스텀 헤더 등)를 보낸다면, 경비실에서는 혹시 위험한 물건일까 봐 바로 들여보내 주지 않고, 친구에게 먼저 연락해서 확인함.이 확인 전화가 바로
OPTIONS 예비 요청(Preflight Request)입니다.가. 단순 요청 (Simple Requests)
GET, POST 같은 일부 간단한 요청은 한 번에 처리됨- 브라우저 → 서버
- 브라우저가
https://api.example.com으로 데이터를 요청하며, 요청 헤더에 자신의 출처를 담아 보냄 Origin: https://my-app.com
- 서버 → 브라우저
- 서버는 요청을 받고,
Origin헤더를 확인함. - 만약
https://my-app.com요청을 허용한다면, 응답 헤더에 허락의 표시를 담아 보냄. Access-Control-Allow-Origin: https://my-app.com
- 브라우저의 판단
- 브라우저는 응답 헤더의
Access-Control-Allow-Origin값을 보고 서버가 요청을 허락했는지 확인.
값이 일치하면 데이터를 정상적으로 처리하고, 일치하지 않거나 헤더가 없으면 CORS 에러를 발생시키고 데이터를 폐기함
나. 프리플라이트 요청 (Preflight Requests)
PUT, DELETE 처럼 서버 데이터에 영향을 줄 수 있는 '위험한' 요청이나, 커스텀 헤더가 포함된 요청의 경우,브라우저는 본 요청을 보내기 전에 "이런 요청을 보내도 괜찮을까요?" 라고 묻는 예비 요청(preflight request)을 먼저 보냄.
이 예비 요청은
OPTIONS라는 HTTP 메소드를 사용- (예비) 브라우저 → 서버 (OPTIONS 요청)
- 브라우저가
OPTIONS메소드로 예비 요청을 보냄 - 헤더에는 앞으로 보낼 본 요청에 대한 정보(사용할 메소드, 포함될 헤더 등)를 담음
OPTIONS /resource HTTP/1.1: resource라는 주소에 대해 질문(OPTIONS)이 있다.Origin: https://my-app.com: 나는 my-app.com에서 출발했는데Access-Control-Request-Method: PUT: 내가 보내고 싶어 하는 본 요청의 메소드는PUT이다.Access-Control-Request-Headers: Content-Type: 그리고 본 요청에Content-Type헤더를 포함할 예정이다
`OPTIONS /api/items/123 HTTP/1.1` `Origin: https://my-app.com` `Access-Control-Request-Method: PUT` (이제 PUT 요청 보낼 거다) `Access-Control-Request-Headers: Content-Type` (Content-Type 헤더를 사용할 거다)
→
OPTIONS는 이 요청 자체의 메소드이고, Access-Control-Request-Method는 앞으로 보낼 본 요청의 메소드를 알려주는, 질문의 내용
- (예비) 서버 → 브라우저
- 서버는 이 예비 요청을 받고 자신이 허용하는 정책을 응답 헤더에 담아 알려줌.
https://my-app.com에서 오는 요청은 허용한다.PUT메소드도 괜찮고,Content-Type헤더를 사용하는 것도 허용
`HTTP/1.1 204 No Content` `Access-Control-Allow-Origin: https://my-app.com` `Access-Control-Allow-Methods: GET, POST, PUT, DELETE` (이 메소드들은 허용함) `Access-Control-Allow-Headers: Content-Type` (이 헤더는 사용해도 괜찮다)
- 브라우저의 판단 및 본 요청
- 브라우저는 서버의 허용 정책을 확인함. 자신이 보내려던 본 요청(
PUT,Content-Type헤더 사용)이 허용 범위에 포함되어 있으면, 그때서야 진짜 본 요청(PUT)을 서버로 보냄. - 만약 허용되지 않으면, CORS 에러를 발생시키고 본 요청은 아예 보내지 않음
- 브라우저가 서버로부터 허락 응답을 받으면, 그제야 원래 보내려 했던 진짜 요청, 즉
PUT요청을 보냄
- SOP (동일 출처 정책): 기본적으로 다른 출처의 리소스 요청을 막는 브라우저의 보안 장치.
- CORS (교차 출처 리소스 공유): SOP의 예외 규칙으로, 서버가 허락한 다른 출처에게만 리소스 접근을 허용하는 안전장치.
- 동작 원리: 브라우저와 서버가 HTTP 헤더(
Origin,Access-Control-Allow-Origin등)를 통해 서로 소통하며 권한을 확인하는 방식
- Preflight (OPTIONS 요청): 실제 데이터에 영향을 줄 수 있는 요청 전, 허락을 받기 위해 먼저 보내는 예비 질문
→ 웹 개발 시 CORS 에러가 발생하면 "브라우저가 동일 출처 정책에 따라 요청을 막았고, 목적지 서버가 허락(
Access-Control-Allow-Origin 헤더)을 해주지 않았구나"라고 이해하면 됨CORS의 핵심 동작 원리
브라우저의 자동화된 역할
CORS 정책을 확인하고 강제하는 주체는 브라우저. 개발자는 평소처럼 서버에 데이터 요청 코드(예:
fetch, axios)를 작성하기만 하면 됨→ 클라이언트 코드와 서버 사이에서 보안 미들웨어처럼 동작
정확한 처리 흐름
- 개발자 코드:
fetch('https://api.example.com/data')코드를 실행
- 브라우저 가로채기 (자동): 요청이 실제로 네트워크로 전송되기 전에 브라우저가 이 요청을 가로챔
Origin헤더 추가 (자동): 브라우저는 요청 헤더에 이 요청이 시작된 출처가 어디인지 알려주는Origin헤더를 자동으로 추가함. (예:Origin: https://my-app.com)
- 서버 응답: 서버는 요청을 받고, 설정된 CORS 정책에 따라 응답 헤더에
Access-Control-Allow-Origin등을 포함하여 응답함
- 브라우저 검사 (자동): 응답이 개발자 코드에 도달하기 전에 브라우저가 먼저 응답 헤더를 검사
- 허용:
Access-Control-Allow-Origin헤더 값이Origin과 일치하면, 브라우저는 이 응답을 안전하다고 판단하고 개발자 코드에게 전달.fetch는 성공적으로 완료됨 - 차단: 헤더가 없거나 값이 일치하지 않으면, 브라우저는 이 응답을 위험하다고 판단하고 데이터를 폐기한 뒤, 개발자 콘솔에 CORS 에러를 띄움
→ 이 모든 과정은 개발자가 모르는 사이에 브라우저 보안 엔진에 의해 자동으로 처리됨
프리플라이트 요청과 브라우저 '기억'
매번 본 요청 전에 예비 요청을 보내면 성능이 저하될 수밖에 없음
→ 브라우저는 이 예비 요청(Preflight)의 결과를 일정 시간 동안 '기억' (캐시, Cache)함
"나도 모르는 사이에 빠르게 왔다 갔다 하는" 과정은 최초 한 번만 (또는 캐시가 만료되었을 때) 일어남
Access-Control-Max-Age헤더: 서버는 프리플라이트 요청에 대한 응답을 줄 때, 이 결과를 브라우저가 얼마 동안 기억해도 되는지Access-Control-Max-Age헤더에 초 단위로 명시할 수 있음- 예:
Access-Control-Max-Age: 86400(24시간 동안 기억해도 좋다는 의미)
프리플라이트 캐시 동작 시나리오
- 최초의
PUT요청 - 브라우저: "(예비) 이 도메인에
PUT요청 보내도 되나요?" (OPTIONS요청 전송) - 서버: "네, 허용합니다. 그리고 이 허락은 24시간 동안 유효합니다." (
Access-Control-Max-Age: 86400포함하여 응답) - 브라우저: "알겠습니다." (결과를 캐시에 저장)
- 브라우저: "(본 요청) 데이터 여기 있습니다." (
PUT요청 전송)
- 5분 뒤, 두 번째
PUT요청 - 브라우저: (자신의 캐시를 확인) "24시간 안 지났고, 이미 허락받았으니 또 물어볼 필요 없다."
- 브라우저: "(본 요청) 데이터 또 보냄" (예비 요청 없이 바로
PUT요청 전송)
개발자가 모르는 사이에
OPTIONS 요청이 왔다 갔다 하지만, 성능 저하를 막기 위해 브라우저가 서버의 허락 하에 그 결과를 똑똑하게 기억하고 재사용함