CORS 에러

yellowbutter·2023년 9월 4일

에러⚒️

목록 보기
2/4
post-thumbnail
  • 회사에서 진행한 프로젝트 배포 과정에서 악명높은 CORS 에러를 겪었다.
    react로 개발중었으며 클라이언트에서 백엔드 서버에 보낸 요청이 CORS로 돌아왔다.
    나의 경우 프론트엔드와 백엔드가 같은 포트 8077에서 실행 중이라도, 호스트가 달라서 CORS 에러가 발생했다.
    CORS 에러에 대해 알고 있긴 했지만 다시 한번 정리할 필요가 있을 것 같아 기억을 위해 기록하려고 한다.

📌 1. CORS란?

  1. CORS는 Cross Origin Resource Sharing에 약자다.
    Origin은 url 주소상에서 프로토콜, Domain 이름, 포트까지 포함한 개념이다.

  2. 예시로, 로컬에 nextjs 프론트엔드 서버를 3000번으로 띄웠다면
    http://localhost:3000 이 Origin에 해당한다.

  3. Same Origin이란 같은 Origin을 뜻하고
    Cross Origin 이란 서로 다른 Origin을 뜻하는 말이다.

  4. 여기에 붙은 Resrouce Sharing 의 단어까지 포함하면
    CORS는 서로 다른 Origin간의 리소스 교환을 뜻한다.

🐾 1.1 URL의 구조

URL 구조
출처가 무엇인지 알아보기 위해서는 URL 의 구조를 살펴보아야 한다.
서버의 위치를 의미하는 https://google.com 과 같은 URL 은 사실 여러 요소로 이루어져 있다.

포트번호가 생략이 가능한 이유는 http, https 프로토콜의 기본 포트번호가 정해져 있기 때문이다.
http://velog.com:870 과 같이 포트번호가 명시적으로 표기 된게 아니라면
http는 80번 https는 443번 포트가 디폴트 값이다.

🐾 1.2 출처(Origin)란?

출처는 Protocol, Host, 포트번호를 의미한다.
즉 서버의 위치를 찾아가기 위해 필요한 가장 기본적인 것들을 합쳐놓은 것이다.

-> 콘솔창에 location.origin 실행하면 출처 확인할 수 있다.

http / https -> 프로토콜
naver.com / google.com -> 호스트
:80 / :81 / :443 -> 포트번호

⚒️ 나의 경우 프론트엔드와 백엔드가 같은 포트 8077에서 실행 중이라도, 호스트가 달라서 CORS 에러가 발생했다.

📌 2. CORS란?

다른 출처의 리소스 공유에 대한 허용/비허용 정책

  1. 같은 Origin상에서는 요청이 오는 곳과 처리하는 곳이 동일하기 때문에 특별히 보안상 처리해줄 이유가 없다.
  2. 그런데 다른 Origin에서 오는 요청이라면 동일 출처 정책(Same-Origin Policy)에 의해 보안 검증이 필요하다다. 이때 CORS가 사용된다. 서버는 클라이언트의 요청 헤더에 담긴 Origin 정보를 확인하고, 해당 Origin이 서버의 자원에 접근할 수 있는지 결정한다.
  3. 제약이 없다면 해커가 CSRF, XSS 등의 공격 방법을 이용하여 사용자의 개인 정보를 가로챌 수 있다.
  4. 브라우저는 SOP(Same-Origin Policy)을 따라, 다른 출처의 스크립트나 리소스가 실행되지 않도록 차단한다. CORS는 이 정책을 완화하여, 특정 조건 하에서 다른 출처의 리소스에 접근할 수 있게 허용한다.
  5. SOP 정책을 위반하지 않는 한, CORS 정책에 따라 서버에서 허용한 다른 출처의 리소스에 대한 접근은 허용된다. 이를 통해 필요한 리소스를 안전하게 공유할 수 있다.

📌 CORS의 동작원리

1. 클라이언트 요청

클라이언트가 HTTP 요청을 보낼 때, HTTP 요청의 헤더에 Origin을 담아 전달한다.

2. 서버 확인

서버는 요청을 받고, Origin 정보를 기반으로 해당 요청이 허용되는지 검사한다.

3. 응답 설정

서버는 요청을 받고, Origin 정보를 기반으로 해당 요청이 허용되는지 검사한다. 허용된 Origin일 경우, 응답헤더에 Access-Control-Allow-Origin을 담아 클라이언트로 전달한다. 이를 통해 해당 Origin이 서버에 접근할 수 있는 권한이 있는지 알려준다.

4.정보 비교 및 처리:

브라우저 내에서: 클라이언트가 서버로부터의 응답을 받으면, 브라우저는 해당 응답의 Access-Control-Allow-Origin 값을 검사하여 요청한 Origin과 일치하는지 확인한다.
일치하는 경우: 브라우저는 응답을 클라이언트에게 전달한다.
일치하지 않는 경우: 브라우저는 보안 상의 이유로 응답을 무시하고 JavaScript 등에서 접근이 차단되는 CORS 에러가 발생한다.

CORS의 시나리오

✔️예비 요청(Preflight Request)

브라우저는 요청을 보낼때 한번에 바로 보내지않고, 먼저 예비 요청을 보내 서버와 잘 통신되는지 확인한 후 본 요청을 보낸다.

즉, 예비 요청의 역할은 본 요청을 보내기 전에 브라우저 스스로 안전한 요청인지 미리 확인하는 것이다.

이때 브라우저가 예비요청을 보내는 것을 Preflight라고 부르며, 이 예비요청의 HTTP 메소드를 GET이나 POST가 아닌 OPTIONS라는 독립적인 요청이 사용된다는 것이 특징이다.

예비 요청의 문제점

API 호출 수가 많으면 많을 수록 예비 요청으로 인해 서버 요청을 배로 보내게 되니 비용 적인 측면에서 폐가 될 수 있다.
따라서 브라우저 캐시(Cache)Visit Website 를 이용해 Access-Control-Max-Age 헤더에 캐시될 시간을 명시해 주면, 이 Preflight 요청을 캐싱 시켜 최적화를 시켜줄 수 있다.

✔️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번이 까다로움)
본 요청을 바로 보내는 Simple Request보다
예비 요청을 먼저 보내는 Preflight Request를 더 자주 접할 수 있다.

CORS를 해결하는 방법 총정리

1. chrome 확장 프로그램

allow CORS / cors unblock
브라우저 오른쪽 상단에서 확장 프로그램을 활성화 시킬 수 있다. 해당 프로그램을 활성화 시키게 되면, 로컬(localhost) 환경에서 API를 테스트 시, CORS 문제를 해결할 수 있다.

처음에 CORS 에러가 생겼을때 cors unblock 확장 프로그램으로 우선 개발을 한 뒤에 cors 에러를 마지막으로 해결했다. cors 에러로 머리가 아플때는 개발 시점에서는 사용해도 좋다고 생각한다.

2. 프록시 사이트 이용하기

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

이런 경우, 모든 출처를 허용한 서버 대리점인 프록시(Proxy) 서버를 통해 요청을 할 수 있다.

현재 무료 프록시 서버 대여 서비스들은 모두 악용 사례 때문에 api 요청 횟수 제한을 두어 실전에서는 사용하기 무리이다. 따라서 실전에서는 직접 프록시(Proxy) 서버를 구축하여 사용해야 한다.

heroku 프록시 서버
http://cors-anywhere.herokuapp.com
서비스를 가장 간단하게 사용할 수 있었으나 2021.02부터는 한 번 들어가서 허용 버튼을 눌러야 가능하다고 하며, cors-anywhere 프로젝트를 fork하여 heroku에서 프록시서버를 만들 수도 있다.

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

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

서버의 종류도 노드 서버, 스프링 서버, 아파치 서버 등 여러가지가 있으니, 이에 대한 각각 해결책을 나열해본다.

각 서버의 문법에 맞게 위의 HTTP 헤더를 추가해 주면 된다.

참고로 CORS에 연관된 HTTP 헤더 값으로는 다음 종류가 있다. (이들을 모두 설정할 필요는 없다.)

# 헤더에 작성된 출처만 브라우저가 리소스를 접근할 수 있도록 허용함.
# * 이면 모든 곳에 공개되어 있음을 의미한다. 
Access-Control-Allow-Origin : https://naver.com

# 리소스 접근을 허용하는 HTTP 메서드를 지정해 주는 헤더
Access-Control-Request-Methods : GET, POST, PUT, DELETE

# 요청을 허용하는 해더.
Access-Control-Allow-Headers : Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization

# 클라이언트에서 preflight 의 요청 결과를 저장할 기간을 지정
# 60초 동안 preflight 요청을 캐시하는 설정으로, 첫 요청 이후 60초 동안은 OPTIONS 메소드를 사용하는 예비 요청을 보내지 않는다.
Access-Control-Max-Age : 60

# 클라이언트 요청이 쿠키를 통해서 자격 증명을 해야 하는 경우에 true. 
# 자바스크립트 요청에서 credentials가 include일 때 요청에 대한 응답을 할 수 있는지를 나타낸다.
Access-Control-Allow-Credentials : true

# 기본적으로 브라우저에게 노출이 되지 않지만, 브라우저 측에서 접근할 수 있게 허용해주는 헤더를 지정
Access-Control-Expose-Headers : Content-Length

⭐ CORS에러는 브라우저에서만 발생⭐ 한다는 것.. 꼭 기억하자.

😥 나는 만든 페이지를 개발 + https에 배포까지 해야하는 상황이었다. 3가지 이외의 방법인 http-proxy-middleware는 로컬 환경일 경우 한정해서 해결할 수 있는 등 개발 + 배포 두가지 상황에서 cors를 만족스럽게 해결할 수 있는 방법은 서버에서 헤더를 세팅하는 방법 뿐이었다.

chrome extension으로 cors 에러를 임시 해결해서 먼저 페이지 개발부터 진행했다. 그 후 에러에 대한 설명과 함께 백엔드 분께 헤더에 코드 추가를 부탁드렸다. 놀랍게도 백엔드분께서 코드를 추가하시자마자 바로 해결되었다.

개발하면서 꼭 만나야하는 cors 그래도 만나서 반갑다 😂

참고자료
악명 높은 CORS 개념 & 해결법 - 정리 끝판왕
CORS 에러를 프론트에서 해결? 무시? 아무튼? 해보자!
어쩌다 마주친 CORS 에러
지긋지긋한 CORS error 이제 제대로 공부해보자
CORS란? CORS 에러란?

profile
기록은 희미해지지 않는다 🐾🧑‍💻

0개의 댓글