CORS란 무엇일까?

shin6403·2021년 1월 30일
0
post-thumbnail

MDN에 그대로 적혀 있는 내용을 가지고 왔다.
이렇게 CORS라는 것은 웹,프론트,서버 개발자라면 꼭 알아야 하며, 중요한 내용을 다루기 전에 CORS가 나오게 된 배경에 대해 알아보자.

옛날에는 서버에 자료를 요청할 때는 같은 도메인에서 API를 요청했다. 옛날에는 다른 도메인에서 요청하면 악의적인 행동을 하는걸로 의심을 할 때 였다.
그렇기에 웹브라우저는 같은 도메인이 아니면 요청을 막았다.
그런데 점점 어플리케이션이 커지면서 기존의 웹브라우저들의 정책이 불편해지기 시작했다.

그래서 "공식적으로 루트를 열어줄테니 거기서 서로 다른 도메인의 자원을 요청해라" 취지에서 나온게 CORS 이다.

서로 다른 도메인에서 리소스 요청을 보내고 받기 위해서는 프론트의 경우 Request Header에 CORS 관련 옵션을 넣어주고 서버에 경우 Response Header에 해당하는 프론트의 요청을 허용한다는 내용을 넣어준다

1. CORS?

Cross - Origin Resource Sharing(교차 출처 자원 공유)

결론적으로는 다른 출처들에 대한 자원을 어떻게 받을 것인가, 모든 출처에 대해 받는건 보안상 위험하니 원하는 출처들의 자원만 받는 정책이라 생각하면 된다.

여기서의 교차(다른), 자원, 공유라는 말은 해석이 가능하지만, 출처라는 말이 살짝 낯설게 느껴진다.

위와 같은 url이 있고, 각 url 마다 각각의 구성요소가 있다.
이 구성요소들 중에 출처를 판단하는 것은 Protocol,Host,Port가 결정하고, Protocol,Host,Port가 같다면 출처가 같고 다르면 다른 출처라고 이야기한다.
(Port 부분은 조금 다른 부분이 있기 때문에 뒤에서 설명하고자 한다.)

위 이미지와 같이 우리가 어느 홈페이지에서 손쉽게 개발자 부분에 가면 url에 대한 출처를 확인할 수 있다.

1. SOP?

Same-Origin Policy(동일 출처 정책)

CORS 같은 다른 출처가 있다면 Same-Origin Policy는 같은 동일 출처도 있다.
평소 일반적인 웹 같은 경우에는 오픈되어 있는 공간이어서 좋은 리소스들이 많지만, 때로는 나쁜 리소스드로 있을수 있다. 그렇기에 처음에 이야기했던 안좋은 의도를 방지하기 위해 같은 출처인 경우에서만 리소스를 받을수 있게 하였다.

브라우저를 사용할때 HTTP Method 통신을 할때, 이 정책을 따르게 된다.

하지만 웹에는 많은 정보들이 있고 하나의 홈페이지에 다른 정보들도 보여주고 싶은데 그래서 그러한 것들의 예외 조항을 만들어놨고, 그런 예외 조항들이 CORS 정책을 지킨 요청, script,img,style sheet 태그 같은 것들이 있다.

그래서 출처가 같은게 뭐고 다른게 뭐야?

같은 출처 vs 다른 출처

아래 예시를 보며 같은 출처와 다른 출처의 의미를 자세히 알아보자

//같은 출처 vs 다른 출처 비교
//예시
https://sewon.kr

https://sewon.kr/profile //(1)⭕️
https://sewon.kr:440/profile?age=20//(2)⭕️
http://sewon.kr/name //(3)❌
https://api.sewon.kr //(4)❌
https://sewon.kr:8000 //(5) ⚠️  // 인터넷 익스플로어 제외하고 다 같은 출처

이제 1~4번까지 하나하나 따져보자.

  1. 1번은 프로토콜과 호스트와 포트는 생략되어 있지만 출처 구성요소가 같기 때문에 예시와 **같은 출처**라고 볼 수 있다.
  1. 포트번호가 명시되어 있지만, http나 https, port는 생략이 가능하기 때문에 **같은 출처**라고 볼 수 있다.
  2. 3번과 같은 url은 예시 orgin에는 프로토콜이 https인데, 이 부분은 http이기 때문에 프로토콜이 달라서 **다른 출처**가 된다.
  3. 4번은 앞에 host가 다르기 때문에 **다른 출처**이다.
  4. 5번은 포트가 다른데 출처라는 것이 RFC에서 정확한 표준이 없다 보니 Protocol,Host,Port까지 인정해주는건 브라우저가 만든 구현정책에 따라 달라지게 된다. 그렇지만 다행히 현재 거의 모든 브라우저들은 포트까지도 같은 출처로 인정을 하고 있다.(인터넷 익스플로어는 해당 안됨)

CORS 시나리오

CORS의 시나리오는 대표적으로Preflight Request, Simple Request, Credentialed Request 3가지가 있고 이 3가지만 봐도 충분하기 때문에 실질적으로 CORS 시나리오가 어떻게 되어있는지 확인해보도록 하자.

Request 요청에 Origin이라는 Header가 있고, Response에 Access-Control-Allow-Origin가 있다.

Origin == Access-Control-Allow-Origin

OriginAccess-Control-Allow-Origin 같으면 같은 출처라고 브라우저는 인식한다.

위의 내용을 알고 시나리오를 확인해보도록 하자.

1. Preflight Request(예비 요청)

예비요청이라는 뜻이다.
Javascript에서 fetch로 url 자원을 요청하면, OPTION 메소드를 통해서 예비요청을 보내게 되는데, 이때 header에 요청에 대한 Origin(출처)을 같이 보내게 된다.
그리고 서버는 header 중에 Access-Control-Allow-Origin 을 응답을 해주게 된다.
그리고 브라우저에서 이 두가지(Origin,Access-Control-Allow-Origin)가 같다면 같은 출처라고 보기 때문에 CORS 정책에 위배되지 않았다고 판단하여 제대로 된 응답을 하게 된다.

반대로 이 두가지(Origin,Access-Control-Allow-Origin)가 다르다면 브라우저가 정책을 위반하였다고 판단하여 요청을 보내지 않는걸 볼 수 있다.

여기서 서버에서는 200을 보냈지만 브라우저에서는 에러를 던진 상황이 발생될 수 있다.
이럴때 CORS는 서버에서 판단하는게 아닌 브라우저가 판단을 하는 것이기에 발생한다.

정리

  • OPTIONS 메소드로 예비요청을 보내고 본 요청을 보낸다.
  • Origin에 대한 정보 뿐만 아니라 자신이 예비 요청 이후 보낼 본 요청에 대한 다른 정보들도 같이 포함 되어 있다.(ex. Access-Control-Request-Headers,Access-Control-Request-Method 등)
  • 요청에 Origin과 응답의 Access-Control-Allow-Origin을 브라우저가 비교해 출처를 판단하여 다르면 에러를 발생 시키고 접근할 수 있는 출처라면 본 요청을 보내 요청을 처리한다.
  • 서버 사이드 영역이 아닌 브라우저 영역이기 떄문에 서버는 200대의 성공 코드를 반환한다.

2. Simple Request(예비 요청)


처음에 설명한 Preflight Request랑 다른 점은 예비요청이 없고, 본 요청에서 예비요청이 했던 행동을 본 요청에서 모두 다 하는거라고 생각하면 된다.
그리고 브라우저에서 이 두가지(Origin,Access-Control-Allow-Origin)가 같다면 같은 출처라고 보기 때문에 CORS 정책에 위배되지 않았다고 판단하여 제대로 된 응답을 하게 된다.

사실 Simple Request는 제약 사항이 많다.

  1. 요청의 메소드는 GET,POST,HEAD 중 하나여야 한다.
  2. Header중에 Accept, Accept-Lauguage, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width를 제외한 Header를 사용하면 안된다.
  3. 만약 Content-Type를 사용하는 경우에는 application/x-www-form-urlencoded,multipart/form-data, text/plain 만 허용된다.

위에서 말한 2번과 같은 경우, 우리는 보통 JWT를 보내는 헤더가 있는데, 그 헤더 자체를 못쓰게 함으로 RestAPI에서 사용을 못한다.
또 3번에서는 ContentType에 우리가 일반적으로 사용하는 aplication/json이 없기 때문에
RestAPI를 자주 사용하는 우리에겐 Simple Request를 볼 일이 거의 없다.

3. Credentialed Request

다른 부분과 달리 조금 더 보안에 신경을 많이 쓴 요청이다. 기본적으로 fetch나 비동기 API들은 쿠키를 담아서 요청을 보내지 않는다. 그러기 때문에 쿠키를 담을수 있는 옵션 값을 줘야하는데
다음 아래와 같은 옵션 값을 넣어서 보내주게 된다.

위 그림과 같이 실질적으로 fetch안에 credentials:include라는 옵션을 줘서 쿠키를 담는걸 확인할 수 있고, 서버에서는 인증이 제대로 되었다고 하면 200대의 성공 코드를 반환하고 브라우저에서 이 두가지(Origin,Access-Control-Allow-Origin)가 같다면 같은 출처라고 보면 CORS 정책에 위배되지 않았다고 판단하여 제대로 된 응답을 하게 된다.

Credentialed Request에도 제약 사항이 있다. Access-Control-Allow-Origin중에 *을 사용하면 모든 출처에 대해서 정보를 받겠다라고 하게 되는건데, 이 시나리오는 보안적으로 막기 위해 사용하기 때문에 *를 사용할 수 없고 명시적인 url을 기입해줘야 한다.
그리고 응답 헤더에는 반드시 Access-Control-Allow-Credentials:true가 존재해야 한다.

profile
생각하는대로 살지 않으면, 사는대로 생각하게 된다.

2개의 댓글

comment-user-thumbnail
2021년 2월 3일

https://sewon.kr/name //(3)❌
http가 되야할듯하네여

1개의 답글