const response = await fetch("https://velog.io);
구글을 키고 다음과 같은 코드를 실행해보자.
위 그림과 같은 결과가 나올 것이다. 구글과 벨로그는 다른 서버이기 때문이다.
교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제. - 출처: https://developer.mozilla.org/ko/docs/Web/HTTP/CORS
먼저 출처(Origin)에 대해 이야기 하자면,
웹 사이트의 기본 주소를 나타내는데, URL에서 프로토콜, host(도메인), port 이 3가지로 구성되어 있다. 도메인은 물론이고, 프로토콜이 다르거나(HTTP와 HTTPS), 포트번호가 다르더라도 출처는 다르다.
즉, CORS를 좀 더 한국식으로 풀이하자면, 서로 다른(Cross) 출처(Protocol+host+Port) 사이에서 리소스(자원)를 요청할 때, 요청에 대한 허용 여부를 결정하고 이를 브라우저에게 그에 따라 허용 또는 차단할 것을 알려주는 정책
출처 : Protocol + Host + Port
그렇다면 왜 서로 다른 출처에서의 리소스에 대한 공유를 제어하는 걸까? 해당 배경에는 SOP(Same origin Policy)에 대해 알아볼 필요가 있다.
SOP는 단어 그대로, 동일한 출처에 대한 정책이다. 이게 무슨 말이냐면 "같은 서버에 있는 자원만 접근하고, 다른 곳에 있는 자원은 제한하겠다" 라는 의미이다.
SOP가 없다면 어떻게 될까? 우리는 이미 네이버나 구글 등 자주 사용하는 사이트의 경우 로그인을 유지시켜 놓고 사용한다. 해커가 사용자에게 악의적인 사이트로 접근하도록 유도한다. 해당 사이트에 접근하게 되면 악성 스크립트가 자동으로 실행이 되어, 정상 사이트인 portal.example.com으로 특정 요청을 보내, 해커는 원하는 악의적인 작업을 수행할 수 있게 된다.
SOP가 존재한다면, hacker.example.com과 potal.example.com 두 origin이 다르기 때문에, 브라우저는 해당 응답에 대해 차단할 수 있다. 물론 완전히 CSRF나 XSS를 방비하는 것은 안되기 때문에 추가적인 보안 조치는 당연히 해야한다. SOP는 요청에 대한 응답에 대해 Origin을 비교하는 것이기 때문에, 패스워드 변경과 같은 악의적인 행동은 못막음
그렇지 않다. SOP에도 예외 조항들이 존재한다. 그 중 하나가 앞서 설명하였던 CORS이다.
- CORS
- JSONP : script 태그는 SOP 정책에 속하지 않기 때문에, 이를 이용하여 우회하는 방법(권장x, CORS 이전에 사용하던 방식)
이 외에도 document.domain 속성, window.postMessage() 가 있다.
3가지 동작 시나리오가 존재한다.
1. Simple Request
2. 예비 요청(Preflight Request)
3. 인증정보 포함 요청(Credentialed Request)
Simple Request는 웹 브라우저에서 다른 출처로 HTTP 요청을 보낼 때 추가적인 보안 체크를 거치지 않고 간단하게 요청을 보낼 수 있는 경우이다.
브라우저는 Origin과 응답 헤더의 Access-Control-Request-Headers를 비교하여 유효하다면 리소스를 허용하고, 그렇지 않다면 CORS 에러 발생
다음 조건을 만족해야 한다.
- 요청은 GET, HEAD, POST 중 하나
- 표준 헤더 외에 사용자 정의 헤더를 사용해서는 안됨
Accept
Accept-Language
Content-Language- Content-Type은
application/x-www-form-urlencoded, multipart/form-data, text/plain 형식 중 하나여야 함해당 조건을 만족해야만 예비 요청(preflight) 없이 Simple Reuqest를 할 수 있음.
요청을 보내기 전, 예비 요청을 보내서 서버의 허용 여부를 확인한 후 실제 요청을 보냄. 예비 요청은 OPTIONS
메서드를 사용한다.
예비 요청 헤더 목록
Origin : 출처
Access-Control-Request-Method : 실제 요청에서 사용할 메서드
Access-Control-Request-Headers: 실제 요청에서 사용할 headers예비 응답 헤더 목록
Access-Control-Allow-Origin: 허용되는 출처(만약 *이면 모든 출처에 대해 허용)
Access-Control-Allow-Methods: 허용되는 메서드
Access-Control-Allow-Headers: 허용되는 헤더
Access-Control-Max-Age: preflight 요청 캐싱 시간
클라이언트에서 서버에게 자격 인증 정보 관련 헤더를 포함할 때 사용하는 요청
XMLHttpRequest나 fetch API 등은 별도 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더를 함부로 요청에 넣지 않는데, 요청에 인증과 관련된 정보를 담을 수 있게 해주는 옵션이 credentilas 옵션이다.
클라이언트 코드에서 withCredentials 또는 credentials 옵션을 설정하여 Cross-Origin 요청에서도 인증 정보를 포함할 수 있도록 합니다.
// XMLHttpRequest를 사용하는 경우
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.org/resource', true);
xhr.withCredentials = true; // 인증 정보를 포함
xhr.send();
// Fetch API를 사용하는 경우
fetch('https://api.example.org/resource', {
method: 'GET',
credentials: 'include' // 인증 정보를 포함
});
서버측에서도 Credential에 대해 설정해줘야 한다.
응답 헤더의 Access-Control-Credentials 항목을 true로 설정해야 하며, Access-Control-Allow-Origin 헤더 값을
*
로 지정할 수 없다.