CORS?

어디서 많이 본 장면입니다.

에러메시지를 한 번 천천히 읽어볼까요?

https://css-tricks.com 의 box-shadow 라는 문서를 불러오는데 실패했습니다.

Access-Control-Allow-Origin 헤더가 요청한 리소스에 설정되어 있지 않습니다.

Origin https://velog.io 는 그렇기 때문에 리소스에 접근할 수 없습니다.
(글 작성 중에 찍은 사진입니다. 😉)

만약 Opaque response만으로 충분하다면 request 모드를 no-cors로 설정하여 리소스를 CORS가 해제된 상태로 불러오십시오.

여기서 중요한 포인트가 몇 가지 있는데,

첫 번째는 Access-Control-Allow-Origin 헤더가 설정되어 있지 않다는 말이고,

두 번재는 Origin https://velog.io 이 해당 리소스에 접근할 수 없다는 것입니다.

이 말은 반대로 말하면 어떤 조건을 갖춘 Origin 이거나 Access-Control-Allow-Origin 헤더가 설정되어 있다면 해당 자원에 접근할 수 있다는 말이 될 겁니다.

이렇게 웹 상에서 어떤 리소스에 대한 접근을 허용 혹은 거부하는 메커니즘을 CORS (Cross-Origin-Resource-Sharing) 이라고 부릅니다.

추가로 읽어보기:

How it works?

생각해보면 서로 다른 Origin 간에 리소스를 주고 받는 일은 굉장히 흔한 일입니다.

아래 두 사진을 한 번 보시죠.

스크린샷 2018-10-23 오후 9.25.29.png

스크린샷 2018-10-23 오후 9.27.09.png

총 173개의 리소스가 요청되었고 걔 중에는 자바스크립트 파일, css, 이미지 등이 포함되어 있습니다.
(개발자도구를 켠 다음에 Network 탭으로 이동하면 위와 같은 화면을 볼 수 있습니다.)

이 모든 리소스를 https://naver.com 서버에서 관리할 수도 있지만 조금 더 보편적인 구조는 이미지를 저장하는 서버 (아마존의 S3 와 같은 서비스가 이에 해당됩니다) 와 사용자들이 접속하는 서버가 서로 다른 경우입니다.

한 번 리소스 하나를 자세하게 살펴볼까요?

아까 페이지의 아래 부분에 있던 이미지 하나를 클릭해봤습니다.

스크린샷 2018-10-23 오후 9.33.35.png

스크린샷 2018-10-23 오후 9.33.19.png

General 부분을 보면 https://s.pstatic.net 이 실제로 리소스를 가지고 있는 곳임을 알 수 있습니다.

그 아래에 Response Headers 의 두 번째 줄을 보면 Access-Control-Allow-Origin 헤더가 *로 설정되어 있는 것을 알 수 있습니다.

*은 일반적으로 Wildcard 선택자라고 부르며, 모든 곳에 매치가 된다는 뜻입니다.
(CSS에서도 Wildcard를 사용하는 경우가 있죠)

즉, Access-Control-Allow-Origin 헤더가 *으로 설정되어 있다는 말은 어떤 Origin에서 해당 리소스를 요청하더라도 접근이 가능하다는 말이 됩니다.

그렇다면 맨 처음의 요청이 왜 실패했는지 다시 한 번 살펴봅시다.

스크린샷 2018-10-23 오후 9.40.53.png

???

확실히 Response Headers 부분에 Access-Control-Allow-Origin 헤더가 없네요.

Workaround?

그렇다면 Access-Control-Allow-Origin 헤더란 무엇이며,

어떻게 하면 응답에 이 헤더를 집어넣을 수 있을까요?

방법은 아주 간단합니다.

서버에서 특정 Origin 을 허용하도록 설정을 바꾸거나 모든 Origin에 대해 허용하도록 하면 됩니다.

근데 문제가 있습니다.

요청하는 리소스가 구글의 리소스라면 어떡해야 할까요?

방법1 구글에 전화를 해서 내 Origin을 허용하게끔 설정을 바꾼다.
방법2 포기한다... 😂

방법1과 2 모두 불가능하기 때문에 마치 허용되는 Origin에서 요청을 하는 것처럼 내 요청을 해당 서버에 전달해줄 조력자가 필요하게 됩니다.

그렇습니다.

프록시 서버입니다.

참고: 위키백과 - 프록시 서버

프록시 서버란 요청을 받아서 다른 곳으로 요청을 돌려주는 옛날의 전화 교환원과 같은 일을 하는 서버입니다.

프록시 서버는 응답을 돌려주는 일 외에도 헤더를 추가하거나 요청을 허용/거부 (방화벽) 하는 등의 일이 가능합니다.

스크린샷 2018-10-23 오후 9.49.50.png

그렇다면 이 둘은 어떤 관련이 있을까요?

백문이불여일견이죠?

한 번 실제 사례를 살펴봅시다.

CORS Anywhere 라는 깃 저장소입니다.

CORS Anywhere는 Node.js로 작성된 프록시 서버로 Access-Control-Allow-Origin 헤더를 추가해서 돌려줍니다.

그 말은 아까는 허용되지 않아서 불가능했던 요청도 가능해졌다는 말입니다.

해봅시다!

스크린샷 2018-10-23 오후 9.59.32.png

스크린샷 2018-10-23 오후 9.59.39.png

오! 이제 제대로 페이지가 표시됩니다.

어떻게 된 일 일까요?

먼저 Request URLhttps://cors-anywhere.herokuapp.com/ 이라는 부분이 추가되었고,

아까는 찾아볼 수 없었던 Access-Control-Allow-Origin 헤더가 추가되었습니다.

혹은 다른 사람의 프록시 서버를 사용하기 싫다면 직접 프록시 서버를 운영하는 방법도 있습니다.

문제는 프록시 서버라는 중간 단계가 추가되기 때문에 응답속도가 느려질 수밖에 없습니다.

실제로 위의 요청이 완료될 때까지 총 9.55 초가 소요되었는데 프로덕션 환경에서 사용하기에는 너무 느린 속도죠.

Express + React.js App?

저는 Express + React.js 의 스택을 좋아합니다.

자바스크립트만으로 서버와 프론트의 코드를 모두 작성할 수 있기 때문이기도 하고,

두 프레임워크 모두 굉장히 유연한 구조를 바탕으로 개발자에게 다양한 선택의 여지를 주기 때문이죠.

그렇다면 React 앱을 개발할 때는 CORS 문제를 어떤 식으로 대처할 수 있을까요?

사실 위에서도 말했듯이 React 앱에서 서로 다른 Origin 간에 리소스를 요청하는 일은 굉장히 흔합니다.

흔히 API를 사용한다고 표현하는 모든 행위가 여기에 해당되죠.

그렇기 때문에 create-react-app 은 개발 단계의 프록시 설정을 기본적으로 포함하고 있습니다.

자세한 설정은 Fullstack React App 설정하기를 참조하시기 바랍니다.

Production Environment?

앱을 열심히 개발해서 뙇! 호스팅까지 마쳤습니다. (AWS S3에 웹앱 호스팅하기)

그런데 호스팅을 완료한 후에 페이지에 접속했더니 CORS 에러가 발생했습니다! 😵

어떻게 된 일 일까요?

이는 웹앱이 호스팅된 서버와 API 서버의 Origin이 서로 다르기 때문입니다.

가령,

https://d2qx3p4elhdpld.cloudfront.net/ 에 웹앱이 호스팅되어 있다고 합시다.
(제가 예전에 만들었던 i18n 예제 사이트입니다. Cloudfront에 호스팅되어 있습니다.)

same-origin policy (CORS의 기본 설정으로, 동일한 Origin에서 보내는 요청만 허용됩니다.) 에 따르면 https://d2qx3p4elhdpld.cloudfront.net/otherResources 에 보내는 요청만 허용됩니다.

음... 혹시 맨 처음에 no-cors 모드라는 말을 잠깐 했던 것 같은데 그건 어때요?

기억력이 좋은 독자군요.

한 번 no-cors 모드로 맨 처음에 보냈던 요청을 다시 보내봅시다.

스크린샷 2018-10-23 오후 10.26.03.png

스크린샷 2018-10-23 오후 10.26.09.png

상태 코드가 200인데 왜 미리보기에는 아무 것도 표시되지 않을까요?

이는 no-cors 모드의 목적이 아무에게나 cors 요청을 허용하는 것이 아니기 때문입니다.

여기에 대한 답변은 스택오버플로우의 좋은 답변이 있으니 읽어보시는 것을 추천드립니다.

그러므로 no-cors 모드는 선택지에서 제외됩니다.

어차피 기대하는 응답을 얻을 수 없다는 것을 확인했으니 말이죠.

다음 방법은 무엇일까요?

API 서버에 CORS 요청을 허용하는 것입니다.

이는 굉장히 간단합니다.

만약 EC2와 같은 전통적인 서버 환경이라면 CORS 모듈을 활용해서

// cors 모듈의 예시 코드입니다.
var express = require('express')
var cors = require('cors')
var app = express()

var corsOptions = {
  origin: 'http://example.com', // 허용되는 Origin
  optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
}

app.get('/products/:id', cors(corsOptions), function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for only example.com.'})
})

app.listen(80, function () {
  console.log('CORS-enabled web server listening on port 80')
})

위와 같이 미들 웨어를 사용해서 허용되는 Origin을 추가해주기만 하면 됩니다.

참 쉽죠?

그러면 Serverless 환경에서는 어떻게 하나요?

API gateway 리소스에 대한 CORS 활성화 부분을 읽어보시면 됩니다.

몇 번의 설정만으로 CORS 설정이 끝납니다.

마치며

CORS는 이해하는 것이 어렵지 이해하고 나면 해결하는 방법은 크게 어렵지 않습니다.

세 줄 정리

  • CORS는 서로 다른 Origin 간에 리소스를 전달하는 방식을 제어하는 메커니즘이다.
  • CORS 요청이 가능하려면 서버에서 특정한 헤더 (Access-Control-Allow-Origin) 과 함께 응답할 필요가 있다.
  • CORS를 가능하게 하기 위한 방법으로는 1) 프록시 서버 사용 2) 서버에 CORS 설정 허용하기 (해당 Origin 추가) 가 있다.

혹시 이해가 안 되는 부분이 있거나 어떤 주제에 대해 글을 써줬으면 좋겠다하는 부분이 있는 분들은 댓글 남겨주시면 감사하겠습니다. 😊