CORS(Cross Orgin Resource Sharing)

Sanghyeok·2022년 4월 7일
0

FE면접준비

목록 보기
3/3
post-thumbnail

CORS (Cross-Origin Resource Sharing)

CORS ?

로컬에서 프로젝트를 진행하다보면 다음과 같은 에러 메시지를 발견할 수 있다.

Access to fetch at ‘https://testsample.test/test’ 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.

한국말로 나름의 해석을 해보면 다음과 같다.

origin이 http://localhost:3000 인 경로(?)에서 https://testsample.test/test 로의 fetch API 접근이 차단되었다. (CORS 정책에 의해)

우리가 fetch로 보냈던 'Access-Control-Allow-Origin' 헤더 영역이 존재하지 않다라는 알 수 없는 말을 전하면서 말이다.

CORS는 교차 출처 자원 공유에 대한 약자로 여기서 출처를 나타내는 'Origin'은 바로 다음과 같다.

	Origin = Protocol(Scheme) + Host + 		(Port)
			https://           google.com 	(443)    

생각을 해보면,
출처가 다른 어플리케이션이 서로 자원을 마음대로 이용하는 환경은 너무 위험하다(CSRF, XSS)

그래서 등장한 정책이 SOP인데


SOP (Same-Origin Policy)

같은 출처에서만 리소스를 공유할 수 있다.

그러나 웹에서 다른 출처에 있는 리소스를 가져와서 사용하는 일은 매우 흔해서,
무작정 막는 것도 어려워 몇가지 예외 조항을 두고

예외조건을 만족하면 출처가 다르더라도 리소스 요청을 허용하기로 했는데

그 중 하나의 예외 조건이 바로 CORS인 것이다.

쉽게 설명하면, 다른 출처(origin)로 리소스를 요청 => SOP 위반
이에 더해 CORS까지 위반하게 된다면 => 리소스 사용 불가


Port 번호에 대해

위에서 같은 출처를 판단하는 기준은

  • Scheme
  • Host
  • Port

라고 언급한 바 있다.

예를 들어 https://example.github.io:80 처럼 포트 번호가 명시되어 있다면
포트 번호도 판단하고 없다면 브라우저의 독자적 출처 비교 로직에 따라 판단한다.

( IE는 포트번호를 완전 무시한다. IE 시렁)

여기서 핵심은 출처 비교를 서버가 아닌 브라우저가 한다는 것이다.
그렇기 때문에 서버쪽 로그에는 정상적으로 응답을 했다는 로그가 남아도
브라우저에서는 오류가 발생한 것처럼 보일 수 있고 그렇기에 error tracing이 어렵다.


CORS 동작 순서

  1. 리소스를 요청할 때 기본적으로 HTTP 프로토콜을 사용해서 요청, 헤더의 Origin이라는 필드에
    출처를 담아서 보낸다.

  2. 이후 서버가 이 요청에 대해 응답을 할 때 'Access-Control-Allow-Origin'이라는 값에
    '리소스 접근을 허락받은 출처' 즉 이 Origin은 내가 자원 사용 허락해줄게! 라는 값을 전달해준다.

  3. 브라우저는 보냈던 Origin 값과 받은 'Access-Control-Allow-Origin'의 필드 값을 비교해서 여부를 판단한다.

어떻게 보면 간단해 보일 수 있지만 여기서 또 3가지의 시나리오가 있다고 하는데 하..

Preflight

본 요청을 보내기 전 요청이 안전한지 확인하는 절차가 추가된 시나리오

우리가 웹 어플리케이션을 개발할 때 가장 많이 마주치는 시나리오이다.
이 시나리오에서 브라우저는 요청을 보내기 전 '예비 요청'을 서버에 전송한다.

좀 어지러운 것이,
예비 요청이 성공했어도 CORS 정책 위반으로 인한 에러가 발생할 수 있다.
(판단 시점이 예비요청 이후임)

결국 CORS를 판단의 가장 중요한 것은 'Access-Control-Allow-Origin' 값의 유효 여부이다.
(예비 요청을 실패했더라도 유효하면 위반이 아님)

가장 흔한 시나리오이기도 하지만 모든 요청에 상황에서 예비요청을 추가로 보내는 'Preflight' 하는 건 아니다.

Simple Request

위에서 설명한 Preflight 과정에서 예비 요청의 과정만 생략한 것이다.

특정 조건을 만족했을 경우만 가능하다. 특정 조건이란

  • GET/HEAD/POST의 요청 메서드인 경우
  • 헤더 필드값의 제한 (Authorization도 안 됨)
  • 제한된 Content-Type 사용 (application/x-www-form-urlencoded, multipart/form-data, text/plain)

여기서 2, 3번째 조건이 만족하기가 상당히 까다롭다.
헤더 필드값의 제한도 이미 까다롭지만
최근 대부분의 HTTP API는 text/xml, application/json 을 사용하므로 위 세 조건을 모두 만족하는 요청은 사실상 쉽지 않다.

Credentialed Request

인증된 요청을 사용하는 방법

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

우리가 주로 사용하는 비동기 리소스 요청 API인 XMLHttpRequest, Fetch는 별도의 옵션없이 쿠키나 인증에 관련한 정보를 헤더에 담아 요청하지 않는다.

여기서 말한 별도의 옵션이 바로 Credentials 옵션이고 3가지가 있다.

  • Same-Origin

    • 같은 출처 간 요청에서만 인증 정보를 담는다.
  • Include

    • 모든 요청에 인증 정보를 담는다.
  • Omit

    • 모든 요청에 인증 정보를 담지 않는다.

우리가 주로 사용하는 구글 크롬 브라우저의 Credentials 기본 값은
same-origin으로 로컬에서 보내는 요청에는 쿠키 등의 인증 정보가 담기지 않는다.
그래서 'Access-Control-Allow-Origin' 필드에 와일드카드(*)를 사용해도
안전하다고 판단하는 것이다.

근데 Include로 바꾸면 와일드카드 사용 못한다.
명시적 URL을 포함해야 하고, 응답 헤더에 Access-Control-Allow-Credentials:true를 포함해야 한다.

해결 방법

Access-Control-Allow-Origin 세팅하기

서버에서 요청을 보낼 때 Access-Control-Allow-Origin 필드의 값을 제대로 보내주는 것이
CORS의 가장 정석적인 해결 방법이라고 한다.

이때 와일드카드(*)를 사용해 모든 출처에 대한 요청을 허용해놓으면 당장은 편할 수 있지만
보안적으로 상당히 위험할 수 있기에 삼가는 것이 좋다.

Spring, Express, Django 등 이름 있는 백엔드 프레임워크에는 CORS 설정 관련 라이브러리가 존재한다고 하니 어렵지 않게 세팅할 수 있다고 한다.

Webpack Dev Server로 리버스 프록싱하기

이 부분에 대해 아직 학습이 완전하지 않아서 추후에 추가하도록 하겠다.

아마도 package.json에서 proxy 값을 임의로 설정해주는 것과 비슷한 접근인 것 같은데
단순히 우회하는 것이고 근본적인 해결 방법이 아니니 이것도 삼가도록 하자 ~!

마치며

CORS 오류를 마주해서 머리가 어질어질한 경우는 대부분 프론트엔드 개발자(잇츠 미)일 것 같은데
해결 방법은 또 서버에 있다하니 더더욱 어지러운 상황이 아닐 수 없다.

당장의 테스트를 위해 우회할 수는 있지만 근본적으로는 백엔드 개발자의 도움이 필요할 수 밖에 없다.

CORS로 어지러웠던 경험으로 나중에 한 번 꼭 포스팅하면서라도 정리해야지 라고 다짐했는데
이제라도 하게 돼서 뿌듯하다.

이 글의 99.9%는 Evan Moon님의 블로그 글 CORS는 왜이렇게 우리를 힘들게 하는걸까?에서 학습한 후
내 스스로 받아들이는 과정(단어의 사소한 변화.. ?)를 거친 것이므로 더 궁금한 것이 있다면 위 링크를 접속해서 알아보도록 하자.

0개의 댓글