CORS의 정의를 알고 왜 요청이 실패하는지, 어떤 방식으로 요청하는지 알아보자.

💡 CORS의 배경과 필요성
- 도메인에 보내는 HTTP 요청에 대해 브라우저는 해당 도메인과 연결된 모든 HTTP 쿠키를 연결함.
- 이는 인증 및 세션 설정에 특히 유용함.
다음의 상황을 가정해보자!
접근을 막는건 API를 요청하는 사이트도, API를 가진 사이트도 아닌 접속한 사이트를 보여주고 있는 브라우저임!


- facebook-clone.com에 로그인한다고 가정해보자.
- 브라우저는 facebook-clone과 관련된 session cookie를 저장할 것임.
- 이제 facebook-clone을 방문할 때마다 저장된 session cookie를 이용하여 HTTP 요청을 하기 때문에 다시 로그인할 필요가 없어짐.
하지만 facebook-clone에 다른 요청을 보낸다면?
- 브라우저는 다른 요청이 있을 때, facebook-clone에 대해 저장된 쿠키를 보낼 수도 있음.
- 만약 갑작스런 팝업이 떠 실수로 evil-site.com으로 이동했다면?

- evil-site는 facebook-clone.com/api로 요청을 보낼 수 있음.
- 요청이 facebook-clone으로 보내지기 때문에 브라우저는 관련 쿠키를 포함하여 응답을 함.
- evil-site는 session cookie를 보내고 facebook-clone에 대한 인증된 액세스 권한을 얻음.
- cross-site request으로 성공적으로 계정이 해킹되었다!
그냥 다른 도메인의 요청은 전부 막자!
- 출처를 엄격하게 비교하여(출처가 완벽히 같아야) 데이터를 응답해주기로 결정.
- 이를 SOP(Same Origin Policy)라고 함.
- SOP란 다른 출처의 리소스를 사용하는 것에 제한하는 보안 방식임.
출처란?
URL의 
Scheme
, host
, port
를 통해 같은 출처인지 아닌지 판단할 수 있음.
Scheme
,host
,port
하나라도 다르면 다른 출처라고 판단함. 그러니 같은 출처임을 보여주려면 위 3개의 값이 같아야 함.
- 이는 브라우저가 판단하기 때문에 브라우저마다 기준이 다를 수 있음. (ex. IE는 port를 비교하지 않음)
잠깐 퀴즈!
http://localhost
와 동일출처인 URL은?
1. https://localhost
2. http://localhost:80
3. http://127.0.0.1
4. http://localhost/api/cors한계는 없을까?
- 다른 웹사이트의 유용한 API를 주고 받을 수 없음.
- 과거엔
JSONP
라는 트릭을 사용하여 다른 출처임에도 우회하여 사용할 수 있도록 했지만, 보안을 철저히 하고자 했던 SOP의 목적과 달리 우회하여 보안을 다시 악화시키기 때문.
- 이러한 문제에 대한 해결책으로 CORS가 생겨남.
❓ CORS란?
CORS(Cross-Origin Resource Sharing)는 추가 HTTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수있는 권한을 부여하도록 브라우저에 알려주는 체제.
🗣️ CORS의 요청 종류
CORS
는 브라우저에서 출처를 비교하고 판단함.Simple request (단순 요청)
- 한 번의 요청과 응답을 주고 받아 출처를 비교함.
- 대신 안전성을 보장할 수 있도록 다음의 조건을 지정.
- GET이나 POST, HEAD를 사용한 요청.
- 다음 목록에 속하는 헤더로 요청.
Accept
Accept-Language
Content-Language
- 값이
application/x-www-form-urlencoded
이나multipart/form-data
,text/plain
인Content-Type
요청을 보내보자!
- CORS 요청을 보낼 경우 브라우저는 항상
Origin
이라는 헤더를 요청에 추가함.
GET /request Host: anywhere.com Origin: https://javascript.info ...
- 서버는 요청 헤더에 있는
Origin
를 검사하고, 요청을 받아들이기로 동의한 상태라면 특별한 헤더Access-Control-Allow-Origin
를 응답에 추가함.
- 이 헤더엔 허가된 오리진(위 예시에선
https://javascript.info
)에 대한 정보나 *이 명시됨.
- 이때 응답 헤더
Access-Control-Allow-Origin
에 오리진 정보나 *이들어있으면 응답은 성공하고 그렇지 않으면 응답이 실패함.
- 이 과정에서 브라우저는 중재인의 역할을 함.

- 브라우저는 크로스 오리진 요청 시
Origin
에 값이 제대로 설정, 전송되었는지 확인함.
- 브라우저는 서버로부터 받은 응답에
Access-Control-Allow-Origin
이 있는지를 확인해서 서버가 CORS 요청을 허용하는지 아닌지를 확인함.
- 응답 헤더에
Access-Control-Allow-Origin
이 있다면 자바스크립트를 사용해 응답에 접근할 수 있고 아니라면 에러가 발생함.
200 OK Content-Type:text/html; charset=UTF-8 Content-Length: 12345 API-Key: 2c9de507f2c54aa1 Access-Control-Allow-Origin: https://javascript.info Access-Control-Expose-Headers: Content-Length,API-Key
응답 헤더
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
⚠️
Access-Control-Expose-Headers
라는 헤더를 보내야 전달할 수 있는 목록이 있음.
ex) Content-Length
, API-Key
Preflight request (사전 요청)
- 과거엔 웹페이지에서
GET
,POST
이외의 HTTP 메서드를 사용해 요청을 보낼 수 있을거란 상상조차 할 수 없었음.
GET
,POST
이외의 메서드를 사용한 요청이 오면 '이건 브라우저가 보낸 요청이 아니야’라고 판단하고 접근 권한을 확인함.
- 이런 혼란스러운 상황을 피하고자
preflight
요청이라는 사전 요청을 서버에 보내 권한이 있는지를 확인함.
- preflight 요청은
OPTIONS
메서드를 사용하고 두 헤더가 함께 들어감. Access-Control-Request-Method
헤더 – preflight 요청에서 사용하는 메서드 정보가 담겨있음.Access-Control-Request-Headers
헤더 – preflight 요청에서 사용하는 헤더 목록이 담겨있음. 각 헤더는 쉼표로 구별.
- preflight 요청을 허용하기로 협의했다면, 상태 코드가 200인 응답을 다음과 같은 헤더와 함께 브라우저로 보냄.
Access-Control-Allow-Origin
– *이나요청을 보낸
Origin
(ex. https://javascript.info)Access-Control-Allow-Methods
– 허용된 메서드 정보가 담겨있음.Access-Control-Allow-Headers
– 허용된 헤더 목록이 담겨있음.Access-Control-Max-Age
– permission 체크 여부를 몇 초간 캐싱해 놓을지를 명시함. 그럼 브라우저는 일정 기간 동안 preflight 요청을 보낼 수 있음.

두 번씩 요청하는 이유?
브라우저가 거부하기 전 서버가 처리하는 엉뚱한 결과를 방지하기 위함.
요청을 보내보자!
let response = await fetch('https://site.com/service.json', { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'API-Key': 'secret' } });
- 위 요청이 preflight 요청인 이유 3가지.
PATCH
메서드를 사용.Content-Type
이application/x-www-form-urlencoded
나multipart/form-data
,text/plain
가 아님.- 비표준 헤더
API-Key
를 사용함.
//1단계(preflight 요청) OPTIONS /service.json Host: site.com Origin: https://javascript.info Access-Control-Request-Method: PATCH Access-Control-Request-Headers: Content-Type,API-Key //2단계(preflight 응답) 200 OK Access-Control-Allow-Origin: https://javascript.info Access-Control-Allow-Methods: PUT,PATCH,DELETE Access-Control-Allow-Headers: API-Key,Content-Type,If-Modified-Since,Cache-Control Access-Control-Max-Age: 86400 //3단계(실제 요청) PATCH /service.json Host: site.com Content-Type: application/json API-Key: secret Origin: https://javascript.info //4단계(실제 응답) Access-Control-Allow-Origin: https://javascript.info
- 이 모든 과정이 끝나야 자바스크립트를 사용해 실제 응답을 읽을 수 있음.
🔐 자격증명
- 일반적으로 브라우저는 쿠키나 HTTP 인증 같은 자격 증명(credential)에 대해 매우 민감하다고 생각하기 때문에 함부로 보내주지 않음.
- 그럼에도 불구하고 서버에서 이를 허용하고 싶다면,
자격 증명이 담긴 헤더를 명시적으로 허용하겠다
는 세팅을 서버에 해줘야 함.
fetch
메서드에 자격 증명 정보를 함께 전송하려면 다음과 같이credentials: "include"
옵션을 추가하면 됨.
fetch('http://another.com', { credentials: "include" });
- 옵션을 추가하면
fetch
로 요청을 보낼 때another.com
에 대응하는 쿠키가 함께 전송됨.
- 자격 증명 정보가 담긴 요청을 서버에서 받아들이기로 동의했다면 서버는 응답에
Access-Control-Allow-Origin
헤더와 함께Access-Control-Allow-Credentials: true
헤더를 추가해서 보냄.
200 OK Access-Control-Allow-Origin: https://javascript.info Access-Control-Allow-Credentials: true
- 자격 증명이 함께 전송되는 요청을 보낼 땐
Access-Control-Allow-Origin
에 *을쓸 수 없음.
- 위 예시에서처럼
Access-Control-Allow-Origin
엔 정확한 오리진 정보만 명시되어야 함.
- 이런 제약이 있어야 어떤 오리진에서 요청이 왔는지에 대한 정보를 서버가 신뢰할 수 있기 때문.
참고자료 :