본문 바로가기
IT 기본지식

CORS(Cross-Origin Resource Sharing)에 대하여

by 내기록 2023. 12. 8.
반응형

목차 LIST

     

     

    Cross-Origin Resource Sharing (CORS)

    필요성 및 목적

    CORS는 서버의 리소스를 보호하기 위한 메커니즘으로 웹 서버가 어떤 출처(origin)의 웹 페이지가 해당 서버의 리소스에 접근할 수 있도록 허용할지를 결정하는 방법입니다.

    서버가 '어떤 출처(origins)로부터의 요청을 허용할지'를 제어함으로써 데이터의 접근을 관리하기 위해 사용하며, 브라우저는 이러한 서버의 결정을 존중하고, 서버의 설정에 따라 클라이언트 측의 데이터 접근을 제한합니다.

     

    CORS란

    CORS는 HTTP 헤더 기반의 매커니즘으로 origins(domain, scheme, or port) 가 아닌 곳에서 브라우저가 자원을 로드하는 것을 허용할 수 있습니다.

    또한, CORS는 브라우저가 cross-origin 리소스를 제공하는 서버에 실제 요청을 하기 전에, 서버가 그 요청을 허용할지 미리 확인하기 위해 "사전 요청(preflight)"을 보내는 메커니즘으로 진행됩니다. 이 preflight는 보안을 위해 필요한 절차로 웹 사이트 간의 상호 작용에서 중요한 역할을 합니다.

    이 preflight에서 브라우저는 실제 요청에서 사용될 HTTP 메소드와 헤더를 보냅니다.

    cross-origin?
    예를 들어, 브라우저가 'https://domain-a.com' 웹페이지를 로드했다고 가정할 때,
    이 웹페이지에서 'https://domain-b.com/data.json'에 데이터를 요청하는 것은 cross-origin 요청입니다.

    만약, 웹 페이지(https://www.example.com)와 API 서버(https://www.example.com/api)가 동일한 도메인과 프로토콜을 사용한다면, 이들 간의 통신은 same-origin으로 간주되어 CORS 제약을 받지 않습니다.

     

    보안상의 이유로, 브라우저는 자바스크립트와 같은 스크립트 언어를 통해 이루어지는 cross-origin HTTP 요청을 제한합니다.

    예를 들어, XMLHttpRequest와 Fetch API는 같은 origin policy를 따릅니다. 이는 이러한 API를 사용하는 웹 애플리케이션은 오직 동일한 origin에만 리소스를 요청할 수 있다는 것을 의미합니다. 단, 다른 origins의 응답이 적절한 CORS 헤더를 포함한 경우는 예외입니다. 

     

    절차를 살펴보겠습니다.

    1. 요청 발송 : 웹 애플리케이션(ex. JavaScript)은 다른 출처(cross-origin)으로 HTTP 요청을 보낼 수 있습니다. 이는 브라우저의 보안 정책에 의해 직접적으로 차단되지 않습니다.
    2. 브라우저에서의 확인 과정 : 서버로부터 응답이 도착하면, 브라우저는 응답에 포함된 CORS 헤더를 확인합니다.
    3. CORS 헤더의 유무에 따른 처리
      • CORS 헤더가 있는 경우 : 응답에 적절한 CORS 헤더(ex. Access-Control-Allow-Origin)가 포함되어 있으면, 브라우저는 서버의 응답을 허용하고 웹 애플리케이션이 응답으로 받은 리소스에 접근할 수 있도록 합니다.
      • CORS 헤더가 없는 경우 : 응답에 적절한 CORS 헤더가 포함되어 있지 않으면, 브라우저는 보안 정책에 따라 응답을 차단합니다. 이 경우 웹 애플리케이션이 서버로부터 받은 리소스에 접근하는 것은 불가능합니다.

    결론 : 브라우저는 cross-origin 으로 요청을 보내는 것 자체는 차단하지 않고, 해당 요청의 응답을 어떻게 처리할지는 서버로부터의 응답에 포함된 CORS 헤더에 따라 결정됩니다.

     

    header("Access-Control-Allow-Origin: https://domain1.com");
    header("Access-Control-Allow-Origin: *") # allow all domains

     

    https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

     

    Fetch API?
    JavaScript 인터페이스로, 서버로부터 리소스를 비동기적으로 가져오기 위해 사용됩니다. Fetch API는 XMLHttpRequest를 대체하는 강력하고 유연한 방법을 제공합니다.

    간단하게 특징 두 가지를 살펴보면,
    1) 프로미스 기반 : 프로미스(Promise) 기반으로, 비동기 작업을 더 쉽고 깔끔하게 처리할 수 있도록 합니다. 비동기 코드는 동기 코드처럼 읽고 쓸 수 있게 해줍니다.
    2) 강력하고 유연함 : HTTP 요청을 보내고 받는 데 필요한 다양한 옵션을 제공합니다. 헤더 설정, CORS 설정, HTTP 메소드 지정 등을 처리할 수 있습니다.

    fetch('https://example.com/data')
      .then(response => response.json())
      .then(data => console.log(data))
      .catch(error => console.error('Error:', error));​

     

     

    사전요청(Preflight)

     

    브라우저가 서버에게 보내는 사전 요청(preflight)은 서버가 브라우저의 요청을 허용할지 미리 확인하는 작업입니다. 브라우저는 이 preflight를 통해서 서버에 "이 HTTP 메소드를 사용할 수 있나요?" 또는 "이런 종류의 데이터를 보낼 수 있나요?"와 같은 질문을 하는 것입니다.

    서버가 preflight에 대한 응답을 보내는데, 이 응답에는 서버가 허용하는 메소드, 헤더, origin 등에 대한 정보가 포함됩니다. 서버의 응답이 긍정적인 경우 브라우저는 실제 요청을 보내게 됩니다.

     

    또한, 서버는 preflight의 응답을 통해 클라이언트에게 쿠키, HTTP 인증 정보 등의 "자격 증명"을 실제 요청과 함께 보내야 하는지 알릴 수 있습니다.

     

    preflight를 사용하면 서버가 의도치 않은 origin의 요청으로부터 자신의 데이터를 보호할 수 있습니다. 이 과정을 통해 브라우저와 서버는 보다 안전하게 상호작용할 수 있게 됩니다.

     

    CORS 실패

    CORS 실패는 error로 처리되지만 보안상의 이유로, 오류에 대한 구체적인 내용은 JavaScript에서 알 수 없으며 오류가 발생했다는 사실만 알 수 있습니다. 구체적으로 무엇이 잘못되었는지 파악하는 유일한 방법은 브라우저의 콘솔에서 세부 정보를 확인하는 것입니다.

     

    CORS 설정 방식 : Cookie vs JWT

    Cookies

    두 개의 상황을 제시해 보겠습니다.

    WebApp1(https://domain1.com)과 WebApp2(https://domain2.com)이라는 두 개의 web app이 있다고 할 때, webapp1의 HttpResponse(응답) 안에 쿠키를 설정했습니다. webapp2에서 HttpRequest(요청)를 통해 같은 쿠키를 어떻게 읽을 수 있을까요?

    또는 https://domain1.com에 브라우저가 떠있고, https://domain2.com에 서버가 떠 있는 경우에 서로 인증 쿠키를 어떻게 전달할 수 있을까요? 

     

    쿠키 기반 인증에서, 서버는 HTTP응답으로 클라이언트 애플리케이션에 Set-Cookie 헤더를 보냅니다. 클라이언트는 받은 쿠키를 저장한 후, 동일한 서버로부터의 모든 후속 요청에 해당 쿠키를 자동으로 포함시킵니다. 이렇게 동작하기 위해서는 프론트엔드에서 몇 개의 옵션을 보내야 하고, 백엔드에서는 CORS 정책에 따라 발신자를 신뢰할 수 있도록 특정 HTTP 헤더를 설정해야 합니다.

    set-cookie:SESSIONID=F....; Path=/dev/api; HttpOnly

     

    프론트엔드에서는 후속 HTTP 호출에 crossDomain 및 withCredential 옵션을 추가해야 합니다. 

    withCredentials의 기본 값은 false 입니다.

    crossDomain: true # 클라이언트가 다른 도메인으로의 요청을 인식하고 처리하도록 함
    xhrFields: { withCredentials: true }

     

    백엔드에서는 아래와 같은 헤더를 사용해야 합니다.

    header("Access-Control-Allow-Origin: https://domain1.com");
    header("Access-Control-Allow-Credentials: true");
    header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE");
    header("Access-Control-Allow-Headers: Content-Type, *");

     

    이제 모든 후속 request에 인증 cookie가 포함되어 있는 것을 확인할 수 있습니다.

     

    JWT

    백엔드에서 아래와 같은 헤더를 사용하여 다른 도메인의 요청을 허용할 수 있습니다.

    header("Access-Control-Allow-Origin: https://domain1.com");
    header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE");
    header("Access-Control-Allow-Headers: Content-Type, Authorization");

     

    JWT는 주로 Authorization 헤더에 포함되어 전송되기 때문에 Access-Control-Allow-Header 에 Authorization이 포함되어 있어야 합니다.

     

     

     

    References

    https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

    https://medium.com/@sharadokey/understanding-cors-and-cross-origin-cookies-bf36d624da78

    반응형

    댓글