CORS 에러 해결하는 3가지 방법
웹 프로젝트를 진행하다가
fetch api를 이용해 다음 사이트에서 뉴스 기사를 가져오려고 하는데, 아래와 같은 에러가 나타났다.
위 내용은 CORS 정책 위반으로 인해 오류가 발생했다는 것이다.
그래서 CORS 정책이 대체 뭐고 어떻게 해결하면 좋을지 이것저것 찾아보다가 구글에 ‘CORS 에러’로 검색하면 나오는 글들이 내 상황에 맞지 않거나 그대로 진행했는데도 오류가 나서 해결하는데 시간이 오래걸렸다.
혹시라도 나처럼 헤메는 사람이 있을까싶어 블로그에 글을 남기기로 했다.
CORS(Cross-Orign Resource Sharing)
먼저 CORS가 뭔지 알아보자.
자료를 찾아보니 웹 개발자라면 누구나 CORS 에러를 한 번 정도는 경험하게 된다고 한다. 처음에는 굳이 왜 이런 정책을 만들어서 짜증나게 하나 싶었는데 사실 CORS 정책으로 인해 사이트를 조금 더 안전하게 보호할 수 있게 된다.
CORS는 한글로 그대로 해석하면 ‘교차 출처 리소스 공유’라는 의미이다. 쉽게 말하면 교차(다른) 출처에서의 리소스 공유에 관한 정책이라는 의미이다.
즉, 몇 가지 예외로 지정한 출처만 제외하고 기본적으로는 ‘같은 출처’에서만 리소스 공유가 가능하도록 하는 정책을 의미한다.
참고로 CORS는 서버 스펙이 아닌 웹 브라우저 스펙이다. 서버에서는 CORS를 위반하더라도 정상적으로 응답을 받을 수 있지만 이 응답의 파기 여부는 브라우저가 결정한다.
그럼 같은 출처(Same-Origin)와 다른 출처(Cross-Origin)를 구분짓는 기준은 무엇을 말하는걸까?
같은 출처 vs 다른 출처
같은 출처(Origin)를 구분하는 요소는 3가지(프로토콜, 호스트, 포트)이다. 아래의 주소를 예로 들어보자.
https://parkhoh.github.io/spring/maven_gradle/
위에서 3가지 요소를 표시하면,
- 프로토콜(Protocol):
https:// - 호스트(Host):
parkhoh.github.io - 포트(Port):
(생략 가능, 생략 시 80 포트 가정)
프로토콜, 호스트, (포트)만 동일하면 뒷 부분이 다르더라도 같은 출처로 판단한다.
여기서 포트 번호는 생략이 가능하지만 포트 번호가 정확히 명시되어 있다면 포트 번호까지 똑같아야 동일 출처로 인식한다. 생략 시 포트 번호에 관해서는 RFC 문서를 참고하면 도움이 될 것 같다.
아래의 url은 위 url과 동일 출처로 판단한다.
https://parkhoh.github.io/tags
CORS 에러 해결 방법
그럼 해결 방법은 뭘까? 답은 간단하다.
동일한 출처에서 리소스를 요청하거나 다른 출처에서도 리소스를 요청할 수 있도록 설정하면 된다.
그럼 아래에서 차례대로 방법을 소개해보겠다.
외부 proxy 서버 이용
가장 간단하고 따로 서버를 구축할 필요가 없기 때문에 내가 문제를 해결하기 위해서 선택한 방법이다.
외부 proxy를 이용하면 아래 두 조건을 충족할 수 있기 때문에 선택하게 됐다.
- 현재 진행하고 있는 프로젝트에서는 서버 없이
Vanilla JavaScript로만 구현 목표- 서버를 직접 구축하지 않고 누구나 이용 가능하도록 구현 목표
- 요청 보낼 서버에 대한 내부 설정을 직접 할 수 없다. 즉 크롤링 대상 사이트가 내 서버일리가 없다.
원리는 외부에 존재하는 proxy 서버를 이용하면 요청한 URL로 프록싱을 해주고 CORS 정책을 지킨 것처럼 우회할 수 있다.
기능은 거의 비슷하기 때문에 어떤 외부 proxy 사이트를 사용해도 되지만 나는 cors-anywhere라는 사이트를 이용하기로 했다.
세팅 방법은 간단하다.
- 먼저 cors-anywhere 사이트에 접속해
Request temporary access to the demo server버튼을 눌러 일시적인 접근 권한을 받는다.- fetch, XMLHttpRequest 등으로 요청을 보낼 때 URL 주소 앞에
https://cors-anywhere.herokuapp.com/를 붙여준다.
const cors = "https://cors-anywhere.herokuapp.com/";
const url = "https://parkhoh.github.io/";
fetch(cors + url)
.then((response) => response.text())
.then((data) => console.log(data))
이렇게 수정 후 요청을 보내면 정상적으로 응답받을 수 있고 원하는대로 파싱 후 사용하면 된다.
참고로 다른 사이트를 참고해보면 위의 1번 과정 없이 바로 2번을 실행하게 되는데, 최근에 무분별한 CORS Anyware의 사용으로 인해 개발 목적으로만 일시적으로 사용이 가능하다고 한다.
Access-Control-Allow-Origin 헤더 설정
이 방법은 프론트엔드와 백엔드 모두 직접 통제할 수 있을 경우에 적용 가능한 방법이다.
방법은 서버에서 직접 Access-Control-Allow-Origin 헤더의 값에 와일드카드(*)나 원하는 출처를 넣어주면 된다.
Access-Control-Allow-Origin: *
Access-Control-Allow-Origin: http://localhost:4000/
이렇게 설정하면 브라우저에서는 자신이 보낸 Origin과 서버에서 응답한 Access-Control-Allow-Origin을 비교해본 후 적절한 응답이라고 판단한다.
여기서 Access-Control-Allow-Origin을 *로 설정하게 되면 모든 출처와 리소스를 공유한다는 의미이므로 비용적, 보안적인 면에서 굉장히 좋지 않다. http://localhost:4000/과 같이 범용적인 출처를 넣어주는 것도 마찬가지이다.
따라서 로컬에서 테스트할 때 유용하다고 볼 수 있고 배포 시에는 구체적인 출처를 명시해주는 것이 바람직하다고 한다.
직접 proxy 서버 구축
이 방법은 직접 proxy 서버를 구축하는 방법이다.
위에서 언급했듯이 CORS 정책은 브라우저의 스펙이기 때문에 서버를 직접 구축해서 통신하게 된다면 CORS 정책이 적용되지 않는다.
따라서 클라이언트단에서는 proxy 서버에 요청을 보내고, proxy 서버에서 직접 통신을 해서 결과를 응답해준다.
proxy 서버를 구축했지만 아직 배포를 하지 않았고 프론트엔드의 포토 번호와 백엔드의 포트 번호가 일치하지 않는다면 한 가지 설정해야할 것이 더 남았다.
예를 들면 프론트엔드인 React에서는 3000번 포트를 이용하고 Express에서는 5000번 포트를 이용하는 경우처럼 포트 번호가 일치하지 않게 되면 직접 proxy 서버를 구축하더라도 CORS 오류가 발생한다.
해결 방법은 이전에 살펴본 Access-Control-Allow-Origin 헤더를 설정해주는 방법이 있다.
다른 방법은 http-proxy-middleware 라이브러리를 사용해 프록싱을 해주는 방법이다.
const httpProxyMiddleware = require("http-proxy-middleware");
module.exports = (app) => {
app.use("api",
httpProxyMiddleware({
target: "프록싱 하고자 하는 url",
changeOrigin: true,
})
)
}
이렇게 설정하고 로컬 환경에서 /api로 요청을 보내게 되면 원하는 url로 프록싱을 해줘서 CORS 정책을 잘 지키고 있는 것처럼 우회할 수 있다.
프론트엔드 개발자들이 많이 사용하는 webpack-dev-server의 경우 문법은 약간 다르지만 내부적으로는 http-proxy-middleware를 사용하기 때문에 원리는 같다고 한다.
결론
처음 CORS 에러를 마주쳤을 때 ‘이거 간단히 해결할 수 있을텐데’라는 생각을 했다.
그치만 이 방법, 저 방법 적용해보다 잘 안 돼서 어떤 부분은 정확히 작동하고, 또 다른 부분은 오류가 발생하는지 파악하는 것에 시간을 많이 썼다.
결국 공부해보니 어렵지 않은 문제여서 해결했고 아래 사진처럼 원하는 결과물을 얻었다. (프로그래밍은 항상 답을 알고 나면 이걸 왜 몰랐지 싶은 것 같다..)

백엔드에만 집중해서 공부했다면 한동안 마주치지 않았을 문제였을지도 모른다.
그치만 바닐라 자바스크립트를 공부하면서 웹 개발자라면 한 번쯤 마주치는 문제에 대해 해결해봤다는 것과 ajax 요청에 대해 공부해봤다는 것에 의미를 둬야겠다!

댓글남기기