본문 바로가기
Programming/DevOps, Tools

CORS에 대하여

by kghworks 2022. 8. 8.

목차

  • Origin (Same Origin vs Cross Origin)
  • SOP와 CORS
  • 결론
  • 참고

CORS 정책 위반 문구 (크롬 콘솔)

 웹 개발 시 다른 도메인의 API를 호출하면 위와 같은 에러를 자주 봅니다. 흔히 CORS 오류라고들 하는데, 이는 CORS 정책에 대한 미준수일 뿐 CORS 자체가 오류가 아닙니다. 이 포스팅에서는 이 CORS라는 정책에 대하여 알아보고 그 정책을 지키는 법 (= 오류를 피하는 법)을 알고자 합니다.


Origin (Same Origin vs Cross Origin)

 

 위 콘솔 에러 문구를 다시 보겠습니다. 간단히 번역해보면, "Origin (localhost, 내 웹사이트)에서 호출한 외부 API는 CORS 정책에 의해 막혔다(blocked). 요청된 리소스 header에는 Access-Control-Allow-Origin가 없다." 입니다.

 

 Origin이란 서버의 위치입니다. 이때 다음 세 가지 중 하나만 다르더라도 서로 다른 Origin으로 간주합니다.

  • 프로토콜 (http, https)
  • 호스트
  • 포트

 아래처럼 서버 내부에서 통신하는 것은 Same Origin이라고 합니다. 반대로 서버 외부와 통신하는 것을 Cross Origin이라고 합니다.


SOP와 CORS

 

 SOP란 Same Origin Policy의 약자로  동일한 Origin 안에서만 리소스를 공유하라는 정책입니다.

 CORS는 Cross Origin Resource Sharing (교차 출처 리소스 공유)으로 외부 Origin과 리소스를 공유하는 것을 말하며 SOP와 반대되는 것입니다. 만일 CORS를 무분별하게 하게 된다면, 각 웹사이트의 로그인 세션을 탈취한다던지 등의 보안상의 이슈가 발생할 수 있습니다. 

 

 다시 최초의 콘솔 에러 문구를 보면, "요청된 리소스 header에는 Access-Control-Allow-Origin가 없다."를 봅시다. Access-Control-Allow-Origin이란 요청한 리소스의 서버(외부 API 서버) 헤더에 선언하는 것으로 리소스를 어떤 도메인(Origin)에게 허용해줄지 정하는 것입니다. 즉, 내가 요청한 리소스가 있는 API 서버에서 내 도메인을 허용해야 한다는 것이죠. 도메인을 명시할 때는 한 가지만 명시할 수도 있고, * 로 명시하면 모든 도메인에 대한 허용을 의미합니다.

Access-Control-Allow-Origin : *
Access-Control-Allow-Origin : https://kghworks.tistory.com

 

 이제 CORS를 구체적으로 살펴보겠습니다. CORS는 simple requestpreflight로 나뉩니다.

 

simple request

  • http method : GET, HEAD, POST 중 하나
  • 수정한 header : Accept, Accept-Language, Content-Language 중 하나
  • Content-type : application/x-www-form-urlencoded, multipart/form-data, text/plain  중 하나

 예를 들어 foo.example 사이트에서 bar.other의 리소스를 요청한다고 가정합시다.

 

* 요청 내용 : 회원 명단 가져오기

 

그럼 foo.example의 js에 아래와 같은 코드가 작성될 겁니다.

const xhr = new XMLHttpRequest();
const url = 'https://bar.other/resources/memberList/'; //호출할 외부 API

xhr.open('GET', url); 
xhr.onreadystatechange = someHandler;
xhr.send();

simple request

preflight

 cross origin 요청 중 simple request가 아닌 경우 먼저 preflight (미리) 요청을 하게 되는데, OPTIONS메서드를 사용하여 실제 요청을 보내기 전에 실제 요청이 안전한지 확인하는 과정입니다. preflight와 실제 요청은 개발자가 구분하여 처리하는 것이 아니고, 요청 시 한 번에 preflight -> 실제 요청 순서로 진행합니다.

preflight요청을 위한 코드를 작성하겠습니다.

const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://bar.other/resources/memberInfo/');
xhr.setRequestHeader('Ping-Other', 'pingpong'); //비표쥰 HTTP header
xhr.setRequestHeader('Content-Type', 'application/xml');
xhr.onreadystatechange = handler;
xhr.send('<person><name>James</name></person>');

 

 비표준 http header가 들어가 있고, content-type이 application/xml이므로 simple request 할 수 없습니다.  먼저 preflight request / response가 아래와 같이 작동합니다.

preflight

 이 OPTIONS 에는 다음 내용(헤더)을 포함합니다. (별도 명시 안 해도 브라우저가 자동으로 추가)

 

preflight 요청 header

  • Origin : 요청하는 Origin 정보 (simple request 시에도 포함)
  • Access-Control-Request-Method : 실제 요청 시 사용할 Http 메서드
  • Access-Control-Request-Headers : 커스텀 헤더

 

preflight 응답 header

  •  Access-Control-Allow-Origin : 여기에 작성된 출처(도메인)만 리소스 접근 가능, 도메인은 1개만 명시하거나, *로 지정하면 모든 도메인 (출처)으로부터 접근 가능한 리소스임을 의미.
Access-Control-Allow-Origin : *
Access-Control-Allow-Origin : https://kghworks.tistory.com

 

  • Access-Control-Allow-Methods : OPTION의 Access-Control-Allow-Methods  헤더에 대한 응답 결과. 리소스 접근이 가능한 HTTP 메서드를 지정
Access-Control-Allow-Methods: GET, PUT, DELETE

 

  •  Access-Control-Expose-Headers : 브라우저에서 접근 가능한 헤더를 지정 ( , 로 구분)
Access-Control-Expose-Headers: Content-Length, X-My-Custom-Header, X-Another-Custom-Header

 

  • Access-Control-Allow-Headers : option의 Access-Control-Request-Headers 헤더에 대한 응답 결과
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type

 

  • Access-Control-Max-Age : preflight 요청 결과를 캐싱할 수 있는 시간 (초단위)
Access-Control-Max-Age: 60

 

  • Access-Control-Allow-Credentials : Request with Credential 이 사용 가능한지 여부를 나타냄
Access-Control-Allow-Credentials: true

 

 preflight가 완료되면 실제 요청을 합니다.

실제 요청

 


결론

 

  server side에서는 SOP가 아닌  Cross Origin 요청을 염두하여 CORS 정책에 알맞은 헤더를 작성하여 응답에 포함시켜야 합니다. 특히 API를 만들어 외부에 오픈하는 경우라면 API 정책에 따라 CORS 구현은 필수가 될 것입니다. SPRING MVC 구현 중이라면 아래를 참고할 수 있습니다.

https://spring.io/guides/gs/rest-service-cors/

 

Enabling Cross Origin Requests for a RESTful Web Service

this guide is designed to get you productive as quickly as possible and using the latest Spring project releases and techniques as recommended by the Spring team

spring.io

 

 client side에서는 SOP 이 아니라면, 내 도메인이 상대 API의 CORS 정책에 부합하는 요청인지 확인해봐야 합니다. 응답에는 Access-Control-Allow-Origin  헤더에 * 값이 들어있지 않다는 오류가 대부분일 것입니다. 상대 서버가 전체 허용을 하지 않았거나, 내 도메인에 대하여만 명시하여 허용하지 않은 경우입니다.  그러나 순차적으로 다음과 같은 방법으로 이를 회피할 수 있습니다.

 

  • server side, client side 둘 다 개발 중인 경우  : server side에서 CORS가 가능하도록 구현
  • 상대 서버가 CORS 구현이 안되어있는 경우 : ajax 요청 시 jsonp 방식 이용
  • 개발단계에서 사용해도 될 시 : 브라우저 옵션, postman 등의 플러그인 이용

https://www.postman.com/

 

Postman API Platform | Sign Up for Free

Postman is an API platform for building and using APIs. Postman simplifies each step of the API lifecycle and streamlines collaboration so you can create better APIs—faster.

www.postman.com

 


참고

https://www.w3.org/Security/wiki/Same_Origin_Policy

 

Same Origin Policy - Web Security

Same-Origin Policy There is no single same-origin policy. General Principles An origin is defined by the scheme, host, and port of a URL. Generally speaking, documents retrieved from distinct origins are isolated from each other. For example, if a document

www.w3.org

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

 

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

교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라

developer.mozilla.org

 

https://developer.mozilla.org/ko/docs/Web/HTTP/Methods/OPTIONS

 

OPTIONS - HTTP | MDN

HTTP OPTIONS method 는 목표 리소스와의 통신 옵션을 설명하기 위해 사용됩니다. 클라이언트는 OPTIONS 메소드의 URL을 특정지을 수 있으며, aterisk(*) 를 통해 서버 전체를 선택할 수 있습니다.

developer.mozilla.org

 

댓글