[Web] 어쩌다 마주친 CORS에러

혜린·2022년 12월 25일
30

Computer Science

목록 보기
6/6
post-thumbnail

들어가며


기업협업 프로젝트를 하며 💥CORS에러💥를 마주쳤던 경험이 있었다.
프론트엔드 개발자라면 꼭 마주치게 되는 에러 중 하나라는 것을 알게되었는데..
그래서 꼭 제대로 정리해두고 기억해두고 싶은 내용이라 기록을 남긴다.

CORS에러가 무엇인지에서부터
나는 어쩌다 마주친 CORS에러를 어떻게 해결할 수 있었는 지까지 정리해봅시당♪(´▽`)



CORS가 뭐지?


CORS는 Cross-Origin Resourse Sharing(교차 출처 리소스 공유)의 줄임말이다.
따라서 CORS에 대해 이해하기 위해선 Cross-Origin(교차 출처)이
무엇을 뜻하는 지에 대해 먼저 짚고 넘어가는 것이 좋겠다.


❔ Origin

Cross-Origin Resourse Sharing에서 Origin(출처)은
URL에서 프로토콜, 도메인, 포트를 모두 합친 것을 의미한다.
아래와 같은 URL에서 프로토콜, 도메인, 포트는 어느 부분일까?

URL | https://hyerin.com:3000/posts/0426?data=898

  • 프로토콜 : https://
  • 도메인 : hyerin.com
  • 포트 : :3000



❓ Cross-Origin

Origin이 무엇인지에 대해는 이제 알았다. 그럼 Cross-Origin은 무슨 의미일까?
Cross-Origin이란 아래 중 한 가지라도 다른 경우를 말한다.

  • 프로토콜 : http와 https같이 다른 프로토콜
  • 도메인 : domain.com와 other-domain.com 같이 다른 도메인
  • 포트 : 8002포트와 3000포트 같이 다른 포트


🤷🏻‍♀️ 그래서 CORS가 뭔데?

브라우저에서는 보안상의 이유로 cross-origin HTTP 요청(GET, POST등)을 제한한다.
그래서 cross-origin 요청을 하기 위해선 ⭕서버의 동의⭕가 필요하다.

만약 서버가 동의한다면 브라우저에서는 요청을 허락하고,
동의하지 않는다면 브라우저에서 거절한다.

이렇게 허락을 구하고 거절하는 과정은 HTTP-header를 통해 가능한데,
특별한 헤더인 Access-Control-Allow-Origin를 응답에 추가하면 된다.

이러한 정책을 CORS(Cross-Origin Resourse Sharing, 크로스 오리진 리소스 공유)라고 한다.



🔎 CORS가 탄생한 이유

결론부터 말하자면 CORS는 악의를 가진 해커로부터 인터넷을 보호하기 위해 만들어졌다.

CORS는 악의를 가진 해커로부터 인터넷을 보호하기 위해 만들어졌다.

CORS라는 정책 없이 누구나 데이터를 요청할 수 있다고 가정해보자.
그렇다면 😈원래 있던 기존의 사이트와 똑같은 사이트를 만들어,
사용자의 로그인을 유도해 정보를 추출해가는 것
😈은 식은 죽 먹기일 것이다!

따라서, CORS는 필요한 경우에만 서버에 요청이 가능하게 하여
인터넷을 보호하기 위해 필요하다.



CORS 동작원리


1. Preflight Request

Preflight 요청은 웹 개발 시 가장 자주 마주치게 되는 시나리오다.
브라우저는 본 요청을 보내기 전, 이 요청이 안전한 것인지 확인하기 위해
예비 요청본 요청으로 나누어 서버에 전송한다.

🤷🏻‍♀️ Preflight란?
브라우저가 본 요청을 보내기 전에 보내는 예비 요청을 Preflight라고 부른다.

🚚 동작과정
1. fetch API등을 통해 브라우저에게 데이터를 받아오라 명령함
2. 브라우저는 서버에게 예비 요청(Preflight)을 먼저 보냄
3. 서버는 이에 대한 응답으로 자신이 어떤 것들을 허용하고 금지하는지에 대한 정보를 응답 헤더에 담아 브라우저에게 다시 보냄
4. 브라우저는 자신이 보낸 예비 요청(Preflight)과 서버가 응답에 담아준 허용 정책을 비교
5. 안전하다고 판단되면 다시 본 요청을 보냄
6. 서버가 본 요청에 대한 응답을 하면 최종적으로 응답 데이터를 JavaScript에 넘겨줌


2. Simple Request

대부분의 경우 preflight 방식을 사용하지만,
특정 조건들이 전부 만족된 경우 예비 요청 없이 본 요청만으로
CORS 정책 위반 여부를 검사할 수도 있다.

🚚 동작과정
1. fetch API등을 통해 브라우저에게 데이터를 받아오라 명령함
2. 브라우저는 서버에게 바로 본 요청을 보냄
3. 서버는 이에 대한 응답으로 Access-Control-Allow-Origin과 같은 값을 보내줌
4. 브라우저가 CORS 정책 위반 여부를 검사함
(Preflight와 전반적인 과정은 동일하고, 예비 요청의 존재 유무만 다르다!)

그러면 만족해야하는 조건들에는 어떤 것들이 있을까?
아래의 조건을 모두 만족해야만 예비 요청의 과정이 생략될 수 있다.

🤢 만족해야하는 조건들
1. 요청 메소드GET, POST, HEAD 중 하나여야 함.
2. Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width를 제외한 헤더를 사용하면 안됨
3. 만약 Content-Type를 사용하는 경우에는 application/x-www-form-urlencoded, multipart/form-data, text/plain만 허용됨.

그런데 아래의 조건들 중에서도 2번과 3번이 까다로워 만족시키기 어렵다고 한다.
왜냐하면 사용자 인증에 사용되는 Authorization
application/json과 같은 콘텐츠 타입도 사용하면 안되기 때문..

따라서 이러한 조건을 모두 만족시키기 어렵기 때문에
본 요청을 바로 보내는 Simple Request보다
예비 요청을 먼저 보내는 Preflight Request를 더 자주 접할 수 있는 것이다.



CORS 해결방법


1. Chrome 확장 프로그램

처음에 CORS에러를 마주쳤을 때 임시방편으로나마 사용했던 방법이다..
바로 Chrome에서 제공하는 확장 프로그램 Allow CORS를 설치하는 것!

로컬 환경에서는 CORS에러를 해결할 수 있을 것이다.
하지만, 서비스를 로컬 환경에서만 돌리는건 아니니..
사실상 근본적인 문제를 해결해주지는 못한다.
(그것도 모르고 해결했다고 좋아했다가 바로 지웠던 기억이..😂)


2. Proxy 서버 이용

📒 Proxy가 뭔가요?
프록시(Proxy)란 클라이언트와 서버 사이의 서버 대리점 역할을 한다고 보면 된다.

서버에 따로 설정을 안한 상태에서,
클라이언트가 서버에 리로스 요청을 한다면 CORS 에러가 발생할 것이다.

이런 경우, 모든 출처를 허용한 서버 대리점인 프록시(Proxy) 서버를 통해 요청을 하면 되는 것이다! 하지만, 무료 프록시(Proxy) 서버 대여 서비스들은 악용 사례가 있어 직접 프록시(Proxy) 서버를 구축하여 사용해야 한다.

내가 기업협업 프로젝트에서 마주한 CORS에러도
프록시(Proxy)서버를 통해 해결할 수 있었다.


3. 서버에서 Access-Control-Allow-Origin 헤더 세팅

📜 CORS 해결의 정석
직접 서버에서 HTTP 헤더 설정을 통해 출처를 허용하게 설정하는 가장 정석적인 해결책이라고 한다.

서버의 종류마다 해결방법이 있는 것처럼 보이는데.
프론트 단에서 해결할 수 있는 방법에 대한 이야기는 아니기에 이에 대한 내용은 추후에 차차 알아가보고 싶다.
(삐약삐약거리는 나로서..모르겠어요.그냥 모르겠어요..ㅎ)



나의 경험에 대하여


💬 내가 CORS에러를 마주했던 경험에 대한 이야기를 잠깐 하고 글을 마무리하려고 한다. 기업협업 프로젝트 당시 이 에러로 하루종일 애를 먹었었기에..ヽ(*。>Д<)o゜


💥 문제상황

Next.js로 개발을 하던 도중, CORS에러를 마주쳤다. Next.jsgetSeverSideProps()로 API 호출을 하지 않고, 전역 상태 라이브러리 MobX store에서 API 호출해 데이터를 받아와야하는 상황이었다.

store에서 API를 호출해 데이터를 받아오는 함수를 만들어주었다.
그리고 받아온 데이터를 사용할 컴포넌트에서 해당 함수를 호출해 데이터를 사용하고자 했다.

// BrandStore.jsx
getModalBrandList = async () => {
  // 데이터 받아오기
  try {
      const res = await axios({
        method: 'get',
        url: 'http://example.co.kr:8000/brands/all',
      });

      if (res.status === 200) {
        runInAction(() => {
          this.modalBrandList.data = res.data.results;
        });
      }
    } catch (err) {
      console.log(err);
    } finally {
      return this.modalBrandList;
    }
};

// BrandModal.jsx
useEffect(() => {
    getModalBrandList();
}, []);

그랬더니 Access to XMLHttpRequest at 'API 호출 주소' from origin 'http://localhost:3000'has been blocked by CORS policy란 error 메시지를 확인할 수 있었다. 개발자도구의 Network탭에서도 마찬가지로 CORS에러인 것을 확인할 수 있었다.



💢 원인

🤦🏻‍♀️ 그럼 SSR에선 왜 되는건데!!

가장 의문이 들었던 점은 getSeverSideProps()로 API를 호출했을 때는 CORS 에러가 발생하지 않는다는 것이었다.

알고보니 SSR는 브라우저를 거치지 않고
프론트 서버에서 백엔드 서버로 요청을 보내는 것이기 때문에
브라우저의 개입이 없어 CORS 에러가 발생하지 않았던 것이다!



🎀 해결방법

직접 프록시(Proxy)서버를 구축하지 않아도
몇 줄의 코드로 경로를 우회시킬 수 있다니 너무 신기했다.. 갓넥스트✨

  • Next.js에서 Proxy 설정을 해주려면 아래의 코드만 작성해주면 된다😱
  • 이는 Next.js 공식문서를 통해 쉽게 확인하여 적용시킬 수 있다.
  • rewrites는 URL 프록시 역할을 한다는 점!
// next.config.js
module.exports = {
	async rewrites() {
		return [
			{
				source: "/:path*", // 들어오는 요청 경로 패턴
				destination: "http://localhost:5000/:path*", // 라우팅하려는 경로
			},
		];
	},
}

프론트에서 해결하는 이유?
서버에서 Access-Control-Allow-Origin 헤더를 세팅하는 방법도
있다는 것을 알았기 때문에 프론트 단에서 이를 해결한 이유에 대해 궁금해
프론트 사수분께 이를 여쭤보았다.

서버에서 CORS에러에 대한 문제를 해결하는 방법도 있지만,
만약 서비스가 배포된 상태라고 할 때, 수정이 어렵기 때문에 프론트 단에서
이를 해결해준 것이라는 답변을 얻을 수 있었다.



배운점


⭐ CORS에러는 브라우저에서만 발생

🤦🏻‍♀️ SSR과 CSR의 방식에는 백엔드 서버로부터 데이터를 호출하는
주체가 다르기 때문에
SSR의 방식에선 CORS에러가 발생하지 않고,
CSR 방식에서 CORS에러가 발생한다는 것을 알게 되었다.

💁🏻‍♀️ 결론부터 말하자면,
SSR프론트 서버가 백엔드 서버에 데이터를 요청하는 반면
CSR에선 클라이언트(브라우저)에서 백엔드 서버에 데이터를 요청한다.


(1) SSR (Server Side Rendering)

전통적인 SSR 방식은 클라이언트 - 프론트 서버 - 백엔드 서버 간의 통신 순서를 지킨다.

  1. 웹사이트 접속
  2. 프론트 서버가 백엔드 서버한테 데이터를 요청
  3. 프론트 서버백엔드 서버로부터 전달받은 데이터HTML, CSS, JS와 같은 프론트 소스와 함께 클라이언트에게 응답

(2) Client Side Rendering (CSR)

반면에, CSR은 SSR과는 다른 방식으로 통신이 진행되며 다음과 같다.

  1. 웹사이트 접속 & 클라이언트가 백엔드 서버로 직접 데이터 요청
  2. 프론트 서버로부터 프론트 관련 소스만 응답받음 & 클라이언트가 백엔드 서버로부터 데이터 응답받음



🌍 브라우저가 보내는 요청

🤷🏻‍♀️ 그럼 왜 브라우저가 보내는 요청은 그 출처가 다른걸까?

대부분의 브라우저는 요청을 보낼 때 해당 요청이 안전한지 확인하기 위해 미리 찔러보는 작업을 한다고 한다.

바로 미리 요청을 보내보는 것인데, 이를 Preflight(프리플라이트)라고 한다.
(위의 'CORS 동작원리'에서 다루었던 Preflight RequestSimple Request 떠올리기!)

이 때, 서버에서 올바른 응답을 하더라도 응답 header에 Access-Control-Allow-Origin이 없거나, 설정된 Access-Control-Allow-Origin의 값이 요청을 보낸 클라이언트의 도메인과 다르면 브라우저에서 자체적인 오류를 발생시커 요청을 진행하지 않는다고 한다.



📜 참고


이 글은 아래의 글을 바탕으로 공부하며 개인적으로 정리한 글입니다.
이미지에 대한 출처는 아래의 포스팅에 있습니다.

profile
FE Developer

8개의 댓글

comment-user-thumbnail
2022년 12월 28일

역시 정리의 왕ㅎㅎ

1개의 답글
comment-user-thumbnail
2023년 1월 2일

최고에요!

1개의 답글
comment-user-thumbnail
2023년 1월 3일

글이 알기쉽게 잘 정리 되어있네요 :D

1개의 답글
comment-user-thumbnail
2023년 1월 3일

내용이 미쳤습니다. 구독 버튼 없나요? 좋구댓을 못해서 매우 아쉽네요

1개의 답글