CORS, Cross-Origin Resource Sharing

SeokHun·2021년 3월 21일
0

오류의 발생

개발자라면 프로젝트를 진행하고 간단한 통신이라도 다음과 같은 오류가 발생한 것을 한 번이라도 본 적이 있을 것이다.

Access to fetch at ‘http://localhost:8000’ from origin ‘http://localhost:3000’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.

처음 이 오류를 봤을 때, 너무나도 찾아보기 힘들었고 해결하기까지 왜 이러한 짓을 해야했는지 이해가 되지 않았었다.

서버에서 서버로 전송할 때는 매우 잘 됐었는데 브라우저에서 서버로 보낼 땐 안 됐기 때문이였다

CORS

CORS는 Cross-Origin Resource Sharing의 줄임말로 W3C에서 내놓은 정책이라고 한다.

이는 특정 헤더를 통해서 Cross-Origin, 즉 교차 된 출처(Origin)에서 리소스를 공유되는 것이 허용된 것인지를 알 수 있는 방법이다.

Origin, 출처

URL 을 나타내는 출처는 서버의 위치를 찾아가기 위해 필요한 가장 기본적인 것들을 합쳐놓은 것이고 다음과 같이 표현될 수 있다고 할 수 있다.

출처 내의 포트 번호는 생략이 가능한데 이는 각 웹에서 사용하는 HTTP, HTTPS 프로토콜의 기본 포트 번호가 정해져있기 때문이다.

HTTP가 정의된 RFC 2616 문서를 보면 다음과 같이 기본 포트 번호가 함께 정의되어있는 것을 볼 수 있다.

그러나 출처에 포트 번호가 명시적으로 포함되어 있다면 이 포트 번호까지 모두 일치해야 같은 출처라고 인정된다.
하지만 이 케이스에 대한 명확한 정의가 표준으로 정해진 것은 아니다.

Origin 의 구분

사실 두 개의 출처가 서로 같다고 판단하는 방법은 두 URL의 구성 요소 중 Scheme, Host, Port, 이 3가지만 동일하면 된다.

여기서 Scheme은 https:// , http:// 등을 말하는 것 같다.

CORS는 브라우저의 구현 스펙에 포함되는 정책이기 때문에, 브라우저를 통하지 않고 서버 간 통신을 할 때는 이 정책이 적용되지 않는다.

출처를 비교를 하는 것은 각 브라우저에서 아래의 순서로 진행된다.

  1. CORS 정책을 위반하는 리소스 요청

  2. 해당 서버가 같은 출처에서 보낸 요청만 받겠다는 로직을 가지고 있는 경우가 아니라면 서버는 정상적으로 응답한다.

  3. 브라우저가 CORS 정책 위반이라고 판단되면 그 응답을 사용하지 않는다.

CORS 의 동작

  1. 기본적으로 다른 출처의 리소스를 요청할 때는 HTTP 프로토콜을 사용하여 요청을 보내게 된다.
    이때 브라우저는 요청 헤더에 Origin이라는 필드에 요청을 보내는 출처를 함께 담아보낸다

  2. 서버가 응답 헤더의 Access-Control-Allow-Origin 값에 “허용된 출처” 값을 설정해 응답한다.

  3. 브라우저는 자신이 보냈던 요청의 Origin과 서버가 보내준 응답의 Access-Control-Allow-Origin을 비교해본 후 이 응답이 유효한 응답인지 아닌지를 결정한다.

CORS가 동작하는 방식은 자세히 들여다보면 한 가지가 아니라 세 가지의 시나리오에 따라 변경된다고 한다.

Preflight Request

이 방식을 사용하는 브라우저는 요청을 한번에 보내지 않고 예비 요청과 본 요청으로 나누어서 서버로 전송한다.

  • HTTP 메소드 중 OPTIONS 메소드가 사용된다.

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

Simple Request

이 시나리오에 대한 정식 명칭은 없지만 MDN의 CORS 문서에서는 이 시나리오를 Simple Request라고 부르고 있다.

이 방식은 요청을 전송한 뒤 받은 응답에서 Access-Control-Allow-Origin과 같은 값을 보내주면 그때 브라우저가 CORS 정책 위반 여부를 검사하는 방식이다.

이 방식은 아래와 같은 조건이 성립되어야 사용할 수 있다고 한다!

  1. 요청의 메소드는 GET, HEAD, POST 중 하나여야 한다.

  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만 허용된다.

Credentialed Request

이 방법은 CORS의 기본적인 방식이라기 보다는 다른 출처 간 통신에서 좀 더 보안을 강화하고 싶을 때 사용하는 방법이다.

기본적으로 브라우저가 제공하는 비동기 리소스 요청 API인 XMLHttpRequest 객체나 fetch API는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더를 함부로 요청에 담지 않는다.

이때 요청에 인증과 관련된 정보를 담을 수 있게 해주는 옵션이 credentials 옵션이다.

요청에 인증 정보가 담겨있는 상태에서 다른 출처의 리소스를 요청하게 되면 브라우저는 CORS 정책 위반 여부를 검사하는 룰에 다음 두 가지를 추가하게 된다.

  1. Access-Control-Allow-Origin에는 *를 사용할 수 없으며, 명시적인 URL이어야한다.

  2. 응답 헤더에는 반드시 Access-Control-Allow-Credentials: true가 존재해야한다.

CORS 정책 위반 해결

Access-Control-Allow-Origin

서버에서 Access-Control-Allow-Origin 헤더에 요청하는 Origin의 값을 적어서 허용하는 방법이 있다.

Access-Control-Allow-Origin : *
값을 사용하게 된다면 모든 출처의 Resource를 허용하게 되는 것이다...

보안을 위한다면 출처를 정확히 명시해주도록 하자.

Webpack Dev Server로 리버스 프록싱

백엔드에서 Access-Control-Allow-Origin 헤더가 세팅되어도 http://localhost:3000 같은 범용적인 출처를 넣어주는 경우는 드물다.

프론트엔드 개발자는 대부분 웹팩과 webpack-dev-server를 사용하여 자신의 머신에 개발 환경을 구축하게 되는데, 이 라이브러리가 제공하는 프록시 기능을 사용하면 아주 편하게 CORS 정책을 우회할 수 있다.

module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        pathRewrite: { '^/api': '' },
      },
    }
  }
}

웹팩이 요청을 프록싱해주기 때문에 마치 CORS 정책을 지킨 것처럼 브라우저를 속이면서도 우리는 원하는 서버와 자유롭게 통신을 할 수 있다.

마무리

아래의 참고 사이트에서 많은 것을 보고 배워서 이제 정말 이러한 문제에 마주치게 된다면 어떻게 해결해야 할지 잘 알고있을 것 같다!

참고

CORS는 왜 이렇게 우리를 힘들게 하는걸까?

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

0개의 댓글