CORS

burningminor·2023년 8월 24일
15:1 Access to XMLHttpRequest at 
'http://localhost:7070/v1/backend-api' 
from origin 'http://localhost:3000' has been blocked by CORS policy: 
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

프로젝트 중 위와 같은 오류가 발생하였습니다.
CORS에 대해 알아보았습니다.

SOP

The same-origin policy is a critical security mechanism that restricts how a document or script loaded by one origin can interact with a resource from another origin.

대부분의 브라우저는 SOP(Same Origin Policy)라는 보안 정책을 따릅니다.
이를 통해, 특정 자원에는 해당 자원과 동일한 Origin을 가진 스크립트만 접근할 수 있습니다.

위의 사진을 보면, 좌측에 domain-a.com 이라는 Origin을 가진 document가 있습니다.

해당 document에는 두 개의 이미지가 포함되어 있으므로, 이들을 로드하기 위해 각각의 이미지 서버에 요청을 보낼 것입니다.

동일한 Origin인 domain-a.com Web Server에서 보유중인 파란 이미지(자원)에 대해서는 요청을 보낼 수 있지만, 다른 Origin을 가지는 붉은 이미지는 요청을 보낼 수 없습니다. SOP를 위반하기 때문입니다.

SOP의 장점: 보안

왜 굳이 복잡한 SOP 규칙을 만든 걸까요? 가장 큰 이유는 보안입니다.

위의 그림을 봅시다.

여기서 Victim 은 사용자(브라우저), another-webite.com 은 해킹사이트, your-website.com 은 안전한 사이트입니다.

사용자는 your-sebsite 에 비밀번호 등 민감한 정보를 저장하고 있습니다.
그리고 사용자는 현재 로그인 상태이기 때문에, 브라우저에는 Access-Cookie 등 을 저장하고 있으므로 그런 민감 데이터 요청을 보내면 your-website.com은 이를 승인하는 상태입니다.

이 상태에서, 1번처럼 해커에 의해 브라우저는 해킹 사이트에 접속합니다.
즉, 현재 브라우저에는 your-websiteanother-website 가 실행중입니다.

그리고 another-website.comyour-website에 민감 정보를 요청합니다.
앞서 말했다시피 사용자는 로그인 상태이므로 브라우저로 정보를 받아올 수 있습니다.

그런데 5번의 과정에서 SOP가 작동합니다.

  • another-websiteyour-wetsite 는 다른 Origin을 가지고
  • 뒤에서 나올 your-website가 미리 정의한 CORS 규칙도 만족하지 않으므로

해커의 요청을 거부합니다.
즉, 민감 정보는 another-website 로 넘어가지 못합니다.

이처럼 SOP를 통해 알 수 없는 사이트에서 함부로 자원을 요청하지 못하는 것입니다.

Same Origin

아래의 세 가지 요소가 같다면 같은 Origin 으로 파악합니다.

  • 프로토콜
  • 호스트
  • 포트

저에게 발생한 오류의 경우, 프로토콜(http)와 호스트(localhost)까지는 동일했지만, 포트번호가 각각 80807070으로 달랐으므로 다른 Origin으로 판단된 것입니다.


참고로, Same Site는 Same Origin과 다릅니다!

왜냐하면 호스트(도메인)이 전부 일치해야 하는 Same Origin과 달리, Same Site는 도메인의 일부만 일치하면 Same Site라고 결정하기 때문입니다.

예시로, 아래의 두 사이트는 Same Site지만, Same Origin은 아닙니다.

www.myservice.com
api.myservice.com

CORS

하지만 현실적으로 SOP는 많은 제약이 있습니다.

위의 사례는 물론이고, 백엔드와 프론트엔드 서버를 따로 개발할 경우 프론트엔드 서버에서 백엔드 서버로 보내는 요청 역시 SOP를 위반하기 십상입니다.

그래서 등장한게 CORS입니다.
개발자가 미리 지정한 특정한 경우에 한해서는, 서로 다른 Origin 사이에서도 자원이 공유되는 것을 허락하는 것입니다.

How CORS work?

Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that allows a server to indicate any origins (domain, scheme, or port) other than its own from which a browser should permit loading resources.

CORS는 http 헤더로 브라우저와 자원 저장소(서버)가 설정을 주고받습니다.

Client

HttpRequest를 보낼 때, 해당 요청을 보내는 Origin 정보를 Origin 이란 헤더에 표시합니다.

Server

모든 서버는 해당 서버의 자원을 호출할 수 있는 다른 Origin 정보를 미리 가지고 있습니다. 그리고 해당 정보를 응답 헤더의Access-Control-Allow-Origin 필드에 담아 전송합니다.

예를 들어, api.myservice.com 로 보는 요청에 대한 응답 헤더가 다음과 같다면, 해당 서버는 같은 Origin인 api.myservice.com 뿐만 아니라 www.myservice.com Origin의 요청도 받는다는 뜻입니다.

Access-Control-Allow-Origin: http://www.myservice.com

Browser

브라우저는 위의 두 요소가 일치할 때만 CORS를 만족하였다 보고 요청을 진행시킵니다.

Preflight

특정 경우에는 실제 request를 보내기 전에 CORS 허용 여부를 판단하는 Preflight 요청을 보냅니다. 이를 통해, CORS를 만족하지 않는 헤비한 request를 보내는 걸 막음으로써 트래픽을 줄일 수 있습니다.

CORS also relies on a mechanism by which browsers make a "preflight" request to the server hosting the cross-origin resource, in order to check that the server will permit the actual request.

앞서 브라우저는 http 헤더를 비교해 해당 요청이 CORS 규칙을 만족하는지 판단한다 했습니다. 그런데 사실 이 과정은 실제 응답을 보내기 전, Preflight라는 요청을 보냄으로써 이루어집니다.

이후, 허용이 된 요청에 한해서만 실제 HttpRequest를 보내 동작이 이루어집니다.

이 preflight 요청의 Http 메서드는 OPTION을 사용합니다.
그리고 헤더에 실제 요청에서 사용할 매서드와, http헤더 내용을 포함합니다.

아래는 Preflight request Header 예시입니다.

// Method: Option
OPTIONS /resource/foo

// 실제 HttpRequest 매서드
Access-Control-Request-Method: DELETE

// 실제 HttpRequest의 Header에 포할될 내용
Access-Control-Request-Headers: origin, x-requested-with

// Request를 보내는 Origin
Origin: https://foo.bar.org

참고로 Preflight 결과는 일정 기간 캐시도 가능합니다.

예시

쉽게 이해하기 위해 아래의 상황을 기준으로 설명하겠습니다.

  1. Client(브라우저) 가 Domain-X.com 웹 페이지를 요청합니다.

  2. 요청의 응답으로 HTML, CSS(x.css) 등이 반환됩니다.
    브라우저는 Domain-X.com 페이지를 랜더링하기 시작합니다.

  3. 해당 페이지에 Domain-Y.com 의 이미지가 포함되어 있었습니다.

  4. 브라우저는 Domain-Y.com 으로 Http 요청을 보냅니다.
    이 때, 요청을 하는 건 Domain-X.com 서비스이므로, HttpRequest의 헤더에 Origin: Domain-X.com 이 포함됩니다.

  5. Domain-Y.com 은 모든 Origin의 요청을 허용합니다.
    따라서 Access-Control-Allow-Origin: * 를 헤더에 담아 HttpResponse를 보냅니다.

  6. 브라우저는 OrignAccess-Control-Allow-Origin을 비교합니다.

  7. 두 요소가 일치하므로, 해당 요청이 정상적으로 보내집니다.

SpringBoot에 CORS 관련 설정하기

참고로 위에서 발생한 제 문제는 서버에서 규정한 CORS 규칙에 DELETE 가 허용되지 않았었기 때문입니다.

제 서버는 SpringBoot로 작성되었는데, WebMvcConfigurer 를 상속한 클래스에서addCorsMappings 를 오버라이딩 함으로써 가능합니다.

자세한 건 여기를 참고하세요.

정리

정리하자면, CORS 시행은 브라우저가 진행하지만, CORS 규칙은 서버(자원 제공자)가 설정합니다!

그리고 이 정보를 Access-Control-Allow-Origin 필드로 브라우저에게 알려주는 것이죠. 브라우저는 이렇게 받은 허용 규칙과, 요청 Origin을 비교해 CORS 통제를 하는 것입니다.

참고자료

https://developer.mozilla.org/ko/docs/Web/HTTP/CORS

https://www.youtube.com/watch?v=4KHiSt0oLJ0

https://velog.io/@wpdbs4419/WEB-Server-CORS

https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy

https://etloveguitar.tistory.com/112

https://stitchcoding.tistory.com/46

https://bskyvision.com/entry/CORS%EC%99%80-%EA%B4%80%EB%A0%A8-%EC%9E%88%EB%8A%94-preflight-request%EB%9E%80

https://www.packetlabs.net/posts/cross-origin-resource-sharing-cors/

https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request

profile
burning minor

0개의 댓글