CORS 에러와 해결법

seungchan.dev·2022년 6월 8일
5

Frontend

목록 보기
2/7
post-thumbnail

🚫 CORS(Cross Origin Resouce Sharing) 에러

👀 Intro

프론트엔드에서 백엔드서버로 api 요청을 보낼 때 흔히 볼 수 있는 에러들 중 하나이다.

개발하면서는 서로 오리진이 다르니 프론트나 백엔드에서 CORS 에러를 해결해주도록 하면 된다.

라고 대충 알고 넘어갔지만, 이번을 계기로 확실하게 이해하고 정확히 무슨 요인으로 발생하는지 알아보고자 한다.

📪 Origin

CORS 에러를 이해하려면 Origin 에 대해 알아볼 필요가 있다.

[출처] : 모던 자바스크립트 튜토리얼

위 그림에서 볼 수 있듯이 Origin 이란 Protocol, Hostname, Port를 한데 묶은 것을 의미한다. 다음의 간단한 예제를 통해 같은 오리진인지 아닌지 확인해보자

❓ 다음 중 http://localhost와 동일 Origin인 URL은 무엇일까?

정답을 확인해봅시다.

  1. 첫번째 URL은 프로토콜이 https로 다른 프로토콜을 가지고 있으므로 다른 Origin 으로 취급된다.
  2. 두번째 URL은 포트번호가 80이라서 다른 오리진이라고 생각하겠지만 이는 잘못된 생각이다. 우선 http 프로토콜의 경우 포트번호가 생략될 경우 기본적으로 포트 80을 배정받는다고 한다. 따라서 두번째 URL은 동일한 Origin으로 취급된다. ([참고로 https 프로토콜의 경우 443 포트를 기본적으로 배정받는다.](http의 기본 포트가 80, https의 기본 포트가 443인 이유는 무엇일까? - 기계인간 John Grib))
  3. 세번째 URL은 Hostname이 127.0.0,1 로 통상적으로 localhost 를 의미하는 IP주소이다. 따라서 같은 오리진으로 보일 수도 있다. 다만, 브라우저의 경우 에는 같은 오리진인지 판단할때 문자열 값 자체를 기준으로 판단하기 때문에, 다른 Origin 으로 판단 한다.
  4. 네번째 URL의 경우 localhost뒤에 /api/cors 라는 pathname이 붙었을 뿐, 같은 Protocol, Hostname, Port를 가지기 때문에 같은 Origin으로 판단 한다.

⚠️ SOP(Same Origin Policy)

Origin에 대해 이해한 내용을 바탕으로 웹에서 규정되어 있는 SOP에 대해 알아볼 필요가 있다 공식문서에 따르면, SOP는 다음과 같이 정의된다.

동일 출처 정책(same-origin policy)은 어떤 Origin에서 불러온 문서나 스크립트가 다른 출처에서 가져온 리소스와 상호작용하는 것을 제한하는 중요한 보안 방식입니다. 동일 출처 정책은 잠재적으로 해로울 수 있는 문서를 분리함으로써 공격받을 수 있는 경로를 줄여줍니다.

즉 보안상의 이유로, 웹 상에서는 다른 Origin으로 부터 요청 받는 리소스 접근을 제한하는 정책이라고 볼 수 있다. 따라서 위의 URL들 중 첫번째와 세번째 URL이 http://localhost의 리소스에 접근하려 할 경우 SOP에 위반되어 리소스 접근에 제한 받는다. 이러한 상황은 프론트엔드와 백엔드 개발 중에 흔히 직면할 수 있는 상황이다.

예를 들어, 프론트엔드가 localhost:3000으로 개발 중인 상황에서, 백엔드 서버는 localhost:5000에서 호스팅 중인 상황이라면, 이 둘은 서로 포트번호가 달라 다른 Origin으로 취급되어 SOP에 위반되게 되는 것이다. 이러한 상황을 우리가 흔히 말하는 CORS 에러라고 하는 것이다. 그럼, 이렇게 Origin이 다른 상황에서는 어떻게 원하는 리소스에 접근할 수 있을까? 여기서 이제 CORS에 대해 알아갈 필요가 있는 것이다.

🌙 CORS(Cross Origin Resource Sharing)

MDN 공식문서에 따르면, CORS는 다음과 같이 정의된다.

교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제입니다. 웹 애플리케이션은 리소스가 자신의 출처(도메인, 프로토콜, 포트)와 다를 때 교차 출처 HTTP 요청을 실행합니다.

즉, SOP에 위반 되는 상황 속에서도 리소스에 접근하려면, CORS 헤더를 도입하여 이러한 상황을 해결해야 한다. 여기서 CORS헤더를 도입한 경우는 아래와 같은 상황을 의미한다.

Origin이 https://foo.example인 클라이언트에서 https://bar.other 도메인의 리소스를 접근하는 시나리오를 생각해보자. foo.example에서 리소스를 호출하는 코드는 다음과 같다.

const xhr = new XMLHttpRequest();
const url = 'https://bar.other/resources/public-data/';

xhr.open('GET', url);
xhr.onreadystatechange = someHandler;
xhr.send();

이런식으로 리소스 접근 호출을 할 경우, 브라우저가 서버로 전송하는 내용의 헤더는 다음과 같다.

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에서 보내는 요청인지 항상 포함되어 있다. CORS가 도입된 서버가 리소스 요청에 보내는 응답은 다음과 같다.

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

[…XML Data…]

눈여겨 봐야할 정보는 Access-Control-Allow-Origin 가 *로 설정되어 있으며 이는 어느 Origin에서든 bar.other의 리소스에 접근할 수 있다는 것을 의미한다. 특정 Origin만 접근할 수 있도록 하려면, 다음과 같이 헤더를 변경하면 된다. 아래는 https://foo.example이라는 오리진을 가진 URL만 접근할 수 있음을 의미한다.

Access-Control-Allow-Origin: https://foo.example

👂 CORS를 도입하는 방법

지금 까지는 CORS 에러에 대해서 알아봤다면 이제는 해결하는 방법에 대해서 알아보려고 한다. 이 문제를 해결하는 방법은 프론트엔드, 백엔드 양 측에게 모두 존재한다.

프론트엔드 - Proxy Server 도입

Proxy란 어떠한 대상의 기본적인 동작의 작업을 가로챌 수 있는 객체를 의미한다. 이를 이용한 Proxy Server는 서버와 클라이언트 사이에서 클라이언트가 자신을 통해 다른 네트워크 서비스에 간접적으로 접속할 수 있도록 하는 응용 프로그램을 의미한다.

[출처] : 면접을 위한 CS 전공지식 노트

위의 그림에서 볼 수 있듯, 프론트엔드 서버 말단에 프록시 서버를 도입하여, /api, /api2로 보내지는 요청의 Origin을 API 서버의 Origin과 동일하게 하여 SOP를 위반하지 않도록 할 수 있다.

리액트의 경우 http-proxy-middleware 모듈을 이용하여 이를 구현해 낼 수 있다.

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'http://localhost:5000',
      changeOrigin: true,
    })
  );
};

[출처] - https://create-react-app.dev/docs/proxying-api-requests-in-development/

위의 코드를 src 폴더안에 setupProxy.js라는 파일로 작성하면 리액트 서버에 프록시 서버를 도입할 수 있다고 한다.

백엔드 - (1) Access-Control-Allow-Origin 응답 헤더 추가

응답 헤더의 Access-Control-Allow-Origin 항목을 해당하는 Origin으로 설정해주면 CORS를 도입할 수 있다. Express의 경우는 다음과 같이 추가해 줄 수 있다.

app.use((req, res) => {
    res.header("Access-Control-Allow-Origin", "*"); // 모든 도메인 허용
    res.header("Access-Control-Allow-Origin", "http://localhost:3000"); // 특정 도메인 허용
});

다만 "*"를 이용해 모든 도메인을 허용해 놓는 것은 보안상 좋지 못하니 Origin을 특정해 주는 것이 좋다.

백엔드 - (2) CORS 미들웨어 사용하기

Express의 경우, cors 모듈을 사용하여 CORS를 도입할 수 있다.

import express from "express";
import cors from 'cors';

const app = express();

app.use(cors({ origin: [
  'http://localhost:3000',
  'https://nomalog.netlify.app'
]));

위와 같이 CORS를 설정한다면, localhost:3000과 nomalog.netlify.app라는 Origin에 대해 CORS를 허용할 수 있다.

🚩 출처 및 참고자료

교차 출처 리소스 공유 (CORS) - HTTP | MDN

동일 출처 정책 - 웹 보안 | MDN

[10분 테코톡] 🌳 나봄의 CORS - YouTube

React + Express | CORS 설정하기

profile
For the enjoyful FE development

0개의 댓글