본문 바로가기
  • soldonii's devlog
Javascript 공부/TIL

200131(금) : CORS

by soldonii 2020. 1. 31.

지난 글에 이어 이번에는 CORS에 대한 조사를 통해서 왜 오류가 발생했는지, 어떻게 해결할 수 있는지 작성해보고자 한다.

 

1. CORS란?

  • CORS(Cross-Origin Resource Sharing)이란, 추가 HTTP 헤더를 사용하여 브라우저에게 한 출처(ex. a)에서 실행 중인 웹 응용 프로그램의 다른 출처(ex. b)의 선택된 자원(ex. json data)에 대한 접근 권한을 알려주는 메커니즘이다.
  • 웹 응용 프로그램은 자신과 다른 출처를 가진 리소스를 요청할 때, cross-origin HTTP 요청을 실행한다.

도대체 이게 무슨 말이냐.. 할 수 있으니 우선 cross-origin HTTP 요청부터 살펴보자면, 예를 들어 http://soldonii.com 이라는 사이트의 프론트엔드 자바스크립트 코드에서 XMLHttpRequest(또는 fetch)를 이용해 http://doniisol.com/data.json 데이터를 요청할 경우가 바로 cross-origin HTTP 요청에 해당된다. 자신(soldonii)과 다른 출처(doniisol)의 리소스(data.json)를 요청하는 행위이기 때문이다.

 

  • 보안 상의 이유로 브라우저는 스크립트 내에서 초기화되는(아마 서버를 거치지 않고 프론트엔드 단에서 요청한 경우를 말하는 듯?) cross-origin HTTP 요청을 제한한다.
  • '올바른 CORS 헤더가 포함된' 다른 출처(doniisol)로부터의 응답을 제외하면, 웹 응용 프로그램은 동일한 출처(soldonii)의 리소스만을 요청할 수 있다.

 

 

  • XMLHttpRequestFetch API 호출
  • 웹 폰트(CSS내 @font-face에서 cross-domain 폰트 사용 시)
  • WebGL textures
  • drawImage()를 사용해 canvas에 그려진 이미지/비디오 프레임
  • CSS Shapes from images

위의 요청의 경우에 CORS가 사용된다.

MDN에 있는 사례로 좀 더 자세히 살펴보자.

 

1) Examples
GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://foo.example
  • 위 코드에서 Origin을 보면 어디에서 요청을 보냈는지 알 수 있다.

 

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml

 

  • 그리고 여기서 Access-Control-Allow-Origin: *을 잘 봐야한다.
  • * 표시가 된 것은 요청의 출처가 어디이든 관계없이 요청에 대한 응답을 보내준다는 의미이다. 만약 http://foo.example 사이트만 허용되었다면, Access-Control-Allow-Origin: https://foo.example와 같은 형태를 띈다.
  • 따라서 CORS와 관련된 오류가 없으려면 request의 header의 Origin의 값이 Access-Control-Allow-Origin에 포함이 되어있어야 한다.

 

2) Preflighted Requests
  • Cross-site request의 경우에는 해당 요청이 preflighted된다.
  • preflighted request는 OPTION 메소드와 함께 타 도메인에 선제적으로 HTTP 요청을 보내는 행위이다.
  • OPTION 메소드와 함께 서버에 요청이 보내지면, 서버는 OPTION 메소드를 통해서 해당 요청을 수용할 것인지 거부할 것인지를 결정하게 된다.
  • OPTIONS 메소드와 함께 preflighted 요청을 보낼 떄, 아래와 같은 정보를 서버에게 보낸다.
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

 

  • Access-Control-Request-Method는 서버에게 preflighted가 아닌 실제 요청(real request)을 할 때, POST 요청 메소드로 요청될 것임을 미리 알려주는 것이다.
  • Access-Control-Request-Headers는 서버에게 실제 요청을 할 때, X-PINGOTHER와 Content-Type 헤더로 보내질 것임을 알려주는 것이다.
  • 이 정보들을 토대로 서버는 실제 요청에 응할지 결정하는 것이다.
  • 만약에 요청에 응할 경우, 아래와 같은 응답을 보낸다.
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400

 

3) 해결책

이를 해결하기 위해서 MDN은 아래와 같은 방법을 제시한다.

  1. Response.url을 사용해서 preflighted request가 어디에서 종료될지를 결정할 simple request를 보낸다.
  2. 1번을 통해 획득한 url을 이용해서 실제 요청을 보낸다.

그런데 아직 정확하게 어떤 방식인지는 잘 모르겠다... 우선은 번역만..

실질적으로 이에 대한 해결책을 준 solution은 stack overflow의 답변에서 찾을 수 있었다.

 

  • 요청을 보내는 프론트엔드 쪽 코드를 서버가 관리하지 않을 경우, response는 필요한 Access-Control-Allow-Origin에 대한 정보가 부족한 채로 돌아오게 된다. 따라서 에러가 발생하는 것이다.
  • 이를 해결하기 위해서는, CORS proxy를 통해서 요청을 우회해서 보내면 된다.
  • 요청을 보낼 떄, 'https://cors-anywhere.herokuapp.com'를 통해서 보내도록 하면, 정상적으로 응답을 받을 수 있다.

아마도 내가 데이터를 가져오려는 사이트는 request의 header에서 authorization이 있는지 확인하는데 내 요청에 그 부분이 없어서 거절된 것 같다..?

 

한번에 다 이해하긴 너무 어려운 듯 하다.ㅠㅠ 어쨋든 내 경우 CORS proxy를 우회하는(?) 방법으로 데이터를 가져오는 것은 쉽게 해결할 수 있었다. 다만 원인을 파보고 싶어서 조사하다가 하루가 다 가버렸다..

계속 조사하면 한도 끝도 없어서 일단 요 정도로 정리하고.. 남은 기간 동안에는 리액트를 이용해서 허접하게나마 얼른 만들어봐야겠다!

댓글