지난 글에 이어 이번에는 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)의 리소스만을 요청할 수 있다.
XMLHttpRequest
나Fetch 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은 아래와 같은 방법을 제시한다.
Response.url
을 사용해서 preflighted request가 어디에서 종료될지를 결정할 simple request를 보낸다.- 1번을 통해 획득한 url을 이용해서 실제 요청을 보낸다.
그런데 아직 정확하게 어떤 방식인지는 잘 모르겠다... 우선은 번역만..
실질적으로 이에 대한 해결책을 준 solution은 stack overflow의 답변에서 찾을 수 있었다.
- 요청을 보내는 프론트엔드 쪽 코드를 서버가 관리하지 않을 경우, response는 필요한 Access-Control-Allow-Origin에 대한 정보가 부족한 채로 돌아오게 된다. 따라서 에러가 발생하는 것이다.
- 이를 해결하기 위해서는, CORS proxy를 통해서 요청을 우회해서 보내면 된다.
- 요청을 보낼 떄, 'https://cors-anywhere.herokuapp.com'를 통해서 보내도록 하면, 정상적으로 응답을 받을 수 있다.
아마도 내가 데이터를 가져오려는 사이트는 request의 header에서 authorization이 있는지 확인하는데 내 요청에 그 부분이 없어서 거절된 것 같다..?
한번에 다 이해하긴 너무 어려운 듯 하다.ㅠㅠ 어쨋든 내 경우 CORS proxy를 우회하는(?) 방법으로 데이터를 가져오는 것은 쉽게 해결할 수 있었다. 다만 원인을 파보고 싶어서 조사하다가 하루가 다 가버렸다..
계속 조사하면 한도 끝도 없어서 일단 요 정도로 정리하고.. 남은 기간 동안에는 리액트를 이용해서 허접하게나마 얼른 만들어봐야겠다!
'Javascript 공부 > TIL' 카테고리의 다른 글
200207(금) : Uncontrolled vs. Controlled Component in React (1) | 2020.02.07 |
---|---|
200206(목) : Static Method vs. Instance Method (0) | 2020.02.06 |
200130(목) : Fetch API (0) | 2020.01.30 |
200129(수) : Deep copy vs. Shallow Copy (0) | 2020.01.29 |
200125(토) : Merge Sort, Quick Sort (0) | 2020.01.25 |
댓글