Cors 에러

민경찬·2024년 1월 8일
3

백엔드

목록 보기
11/19
post-thumbnail

프로젝트를 처음해 본다면 꼭 보게되는 에러 중 하나는 바로 Cors에러이다.

기획이 얼추 정리가 되면 백엔드 개발자와 프론트엔드 개발자는 큰 문제 없이 서로의 개발을 마무리하게 된다.

그러나 API를 연결하기 시작할 때부터 기습적으로 발생하는 Cors에러는 재앙과도 같다.

이 악명높은 Cors에러를 해결해보자.

Cors란?

Cors란 Cross-Origin Resource Sharing의 약자로 한국어로는 교차 출처 리소스 허용이라고 한다.

이 까다로운 용어를 이해하기 위해서는 출처(Origin)가 무슨 말인지 알아야한다.출처(Origin)Http://11.22.33.44:80 처럼 protocol, ip, port 로 이루어지며 프론트 페이지를 받은 곳과 백엔드 API를 요청하는 곳 모두 출처(Origin)라고 한다.

즉, 리액트 개발자라면 http://localhost:3000출처(Origin)라는 뜻이다.

출처(Origin)에서 또 다른 출처(Origin)로 요청을 보내게 된다면 이때 리소스를 공유해 준다는 것이 Cors이다.

그러나 우리가 따로 작업을 해주지 않는다면 당연하게도 출처(Origin)가 다르기 때문에 브라우저는 보안을 이유로 에러를 띄우게 되는데 이것이 바로 악명 높은 Cors에러 이다.

예시를 들어보자면,http://localhost:3000로 보는 페이지에서 우리 백엔드 개발자가 준 출처(Origin)http://11.22.33.44:80으로 요청을 보낸다면 높은 확률 Cors에러가 발생한다.


Cors 에러는 어떻게 해결하나요?

크게 두 가지 선택지가 있다.

1. 출처를 동일하게 한다.

당연하게도 Cors에러는 출처(Origin)가 다르기 때문에 생기는 에러이다. 출처를 동일하게 해준다면 Cors에러는 발생하지 않는다.

app.use(express.static(REACT_BUILD_DIRECTORY));

app.use((req, res, next) => {
	res.sendFile(path.join(REACT_BUILD_DIRECTORY, 'index.html'));
});

위 코드 처럼 백엔드 API 서버에서 리액트 빌드 페이지를 서브하면 된다.

이 경우, http://11.22.33.44:80로 요청하여 페이지를 받게 되며 http://11.22.33.44:80/post/all처럼 API를 요청하게 된다.

이 경우는 Same-Origin Policy라고 한다. 한국어로는 동일 출처 정책이라고 한다.

그러나 이 경우 리액트 페이지 경로와 백엔드 API 경로가 일치하여 문제가 발생할 수 있다. 2번 방법을 적극적으로 사용하도록 하자.

2. Cors 관련 Header를 건드려준다.

브라우저는 http://localhost:3000라는 프론트 출처(Origin)에서 http://11.22.33.44:80라는 백엔드 출처(Origin)로 요청하게 된다면 교차 출처에 대해 리소스 공유를 허용할 지 판단하게 된다. 이 때 브라우저는 Access-Control-Allow-Origin라는 헤더를 확인한다.

즉, 브라우저에서 백엔드 출처(Origin)로 요청을 보내고 응답 헤더에 프론트 출처(Origin)가 담겨 있는지 확인한다.

응답 헤더에 프론트 출처(Origin)를 담아주는 과정을 전역 미들웨어로써 등록할 수 있는데, 더욱 간단하게 npm에서 cors 라이브러리를 사용할 수 있다.

app.use(cors({
  origin: 'http://localhost:3000',
}));

추가적으로,Cors와 관련된 헤더는 더 자세하게 건드릴 수 있으므로 npm 사용법 또는 관련 헤더를 더 찾아보는 것을 추천한다.

https://www.npmjs.com/package/cors


쿠키가 받아지지 않아요.

로그인 시, 쿠키가 발급되지 않거나 쿠키를 강제로 삽입한 후 요청을 보내더라도 쿠키가 서버에서 확인되지 않은 경우가 있다.

이 경우 프론트엔드 개발자와 백엔드 개발자 모두 힘써줘야한다.

1. 프론트엔드에서는...

fetch를 사용하는 경우, credentialsinclude로 보내도록 한다.

fetch(url, {
  credentials: 'include'
});

2. 백엔드에서는...

Access-Control-Allow-Credentials헤더를 true로 설정해준다.

app.use(cors({
  origin: 'http://localhost:3000',
  credentials: true,
}));

그러나 이 경우 크롬에서는 Access-Control-Allow-Origin헤더가 *이면 안된다.

브라우저에서 에러가 발생한다. 서버에서는 아무런 에러가 발생하지 않는다. 주의하도록 하자.

app.use(cors({
  origin: '*', // 브라우저에서 에러 발생
  credentials: true,
}));

쿠키가 여전히 반응이 없어요.

이 경우 브라우저의 보안이 강화되어 https가 아닌 http프로토콜에는 브라우저가 쿠키를 발급해주지도, 보내주지도 않는다.

크롬 최신 버전의 경우 SSL 인증서가 적절하지 않으면 문제가 발생한다. 다음의 해결 방법을 사용할 수 있다.

1. https를 사용하는 프록시

ngrok같은 프록시 서버를 경유하도록 할 수 있다. https프로토콜을 제공하기 때문에 브라우저 보안 정책을 우회할 수 있다.

2. SSL 인증서 발급

도메인을 저렴한 가격에 일단 구매한 후 Let's Encrypt와 같은 무료 인증서를 활용하여 백엔드 서버에 도메인 + 인증서를 붙인다.

그러나 인증서 발급을 위한 도메인이 필요하며 비용이 발생한다.

3. 쿠키를 포기한다.

요청 헤더 중 Authorization이라는 헤더가 존재한다. 인증 관련 토큰을 담기 위해 만들어진 헤더로 쿠키를 통해 전달하는 것이 아닌 직접 담아 전달하는 방법이다.

이 경우 문제를 회피하는 것이며 많은 코드를 수정해야할 수도 있다. 그러나 개발 단계에서 쿠키에 대한 큰 불편함을 가지고 있다면 고려해볼 수 있다.


결론

Cors에러는 백엔드와 프론트엔드와 서로 잘 협력하여 해결해야 한다.


P.S. 첫 프로젝트에는 정말 다양한 장애물이 있습니다. 그러나 하나 둘 이겨내다보면 어느덧 한층 성장한 자신을 볼 수 있습니다.

고생 많으셨습니다.

0개의 댓글