CORS란?
CORS는 Cross-Origin Resource Sharing 의 약자로, 다른 자원들을 공유한다는 뜻이다. 웹페이지 상의 제한된 리소스를 최초 자원이 서비스된 도메인(origin) 밖의 다른 도메인(cross-origin) 으로부터 요청할 수 있게 허용하는 구조이다. 다시말하면, 한 출처에 있는 자원에서 다른 출처에 있는 자원에 접근하도록 하는 개념이다.
앞서 말한 Origin은 특정 페이지에 접근할 때 사용되는 프로토콜, 도메인(host), 포트를 말한다. 그래서 Same-Origin이란 프로토콜, 도메인, 포트가 동일하다는 의미를 가진다. 만일 한가지라도 다르다면 Cross-Origin이다.
HTTP 요청에 대해서 HTML은 Cross-Origin 정책을 따르기에 기본적으로 Cross-Origin 요청이 가능하다. link, img 등과 같은 태그에서 다른 리소스에 접근이 가능한것이 Cross-Origin의 예시이다. 하지만 Script 태그 내에 있는 요청에 대해서는 기본적으로 Same-Origin 정책을 따르기에 Cross-Origin 요청이 불가하다.
Cross-Origin정책을 허용하지 않는것이 보안을 위해 좋다고 여겨졌지만, 현재 대규모 서비스가 늘어나게 되면서 외부 호출이 많아지게 되어 CORS 정책이 생겨나게 되었다.
출처(Origin)란?
- URL 구조
출처를 알기위해 URL의 구조를 먼저 살펴봐야한다. URL의 구조는 아래와 같다.
프토콜의 HTTP는 포트 80, HTTPS는 포트 443을 사용한다.
- 출처
출처(Origin)란 위의 URL 구조에서 Protocol, Host, Port를 합친것을 말한다. 출처는 개발자도구 (F12)의 콘솔 창에서 location.origin을 작성하면 확인할 수 있다.
앞서 동일한 출처, 다른 출처에 대해 설명했었다. Protocol, Host, Port가 동일하면 Same-Origin이고, 하나라도 다르면 Cross-Origin이다.
그렇다면, CORS는 왜 필요한가?
만약 CORS가 없이 모든 도메인에서 데이터를 요청할 수 있게 된다면, 다른 사이트에서 원래의 사이트를 도용할 수 있게 된다. 예를 들어, 기존의 사이트와 완전히 동일하게 동작하도록 하여 사용자가 로그인을 하게 만들고, 해당 세션을 탈취하게 된다면 악의적으로 사용자의 정보를 추출할 수 있게 된다. 이러한 공격을 할 수 없게 만들고, 브라우저와 사용자를 보호하기 위해서 CORS는 필요하다.
즉, 다른 출처의 접근을 막기 위해서 필요한 것이다.
CORS 동작 원리
- Simple Request (단순 요청)
- Preflighted Request (프리플라이트 요청)
- Credential Request (인증정보 요청)
- Simple Request (단순 요청)
단순 요청은 예비 요청을 보내지 않고 서버에 본 요청을 보낸 후, 서버가 응답 헤더에 Access-Control-Allow-Origin 과 같은 값을 보내면 그때 브라우저가 CORS 정책 위반 여부를 검사하는 방식이다. 해당 요청은 다음과 같은 조건을 만족해야한다.
- 요청 메소드는 GET, HEAD, POST 중 하나
- Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width를 제외한 헤더를 사용해서는 안된다.
- 만일 Content-Type을 사용하는 경우, application/x-www-form-urlencoded, multipart/form-data, text/plain 만 허용된다.
- 브라우저는 다른 출처에 자신의 주소 https://www.site.com을 origin에 담아서 요청을 보낸다.
- 서버는 요청을 확인하고 다른 출처 주소 https://www.site.com에 접근이 가능하다는 Access-Control-Allow-Origin에 해당 주소를 담아서 결과를 리턴한다.
* Access-Control-Allow-Origin은 CORS 헤더의 중요 요소 중 하나로, 어떤 요청을 허용할지 결정한다. - 만약 서버가 해당 헤더에 응답하지 않거나, 헤더 값이 요청의 출처와 일치하지 않는 도메인일 경우, 브라우저는 응답을 차단한다.
- Preflighted Request (프리플라이트 요청)
일반적으로 웹 어플리케이션을 개발할때 가장 많이 나오는 요청으로, 해당 요청이 들어오면 브라우저는 요청을 한번에 보내지 않고 예비 요청과 본 요청으로 나누어서 서버로 전송한다. 본 요청을 보내기 전에 보내는 예비 요청을 Preflight라고 하며, 예비 요청에는 OPTIONS 메소드가 사용된다.
- Request 헤더 포함 정보
- origin : 어디서 요청을 했는지 서버에 알려주는 장소
- access-control-request-method : 실제 요청이 보낼 HTTP 메서드
- access-control-request-headers : 실제 요청에 포함된 header
- Response 헤더 포함 정보
- access-control-allow-origin : 서버가 허용하는 출처
- access-control-allow-methods : 서버가 허용하는 HTTP 메서드 리스트
- access-control-allow-headers : 서버가 허용하는 header 리스트
- access-control-max-age : 프리플라이트 요청의 응답을 캐시에 저장하는 시간
- 프리플라이트 요청은 OPTIONS를 사용해 자신의 주소 https://www.api.com?q=test를 보낸다. 또한 origin, access-control-request-method, access-control-request-headers를 같이 보낸다.
- 정상적인 응답으로 access-control-allow-origin, access-control-allow-method, access-control-allow-headers, acces-control-max-age를 응답 받는다.
- 정상적인 요청과 응답이라면 본요청을 보내고 응답받는다.
- Credential Request (인증정보 요청)
인증된 요청을 사용하는 방법으로, CORS의 기본적인 방식보다는 다른 출처 간 통신에서 더 보안을 강화하고 싶을 때 주로 사용하는 요청 방식이다.
XMLHttpRequest 객체나 fetch API는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더를 요청에 담지 않는다. 이때, 요청에 인증 관련 정보를 담을 수 있게 해주는 옵션이 바로 credentials 옵션이다.
해당 옵션에는 3가지 값을 사용할 수 있다.
- same-origin (default) : 같은 출처 간 요청에만 인증 정보를 담을 수 있다.
- include : 모든 요청에 인증 정보를 담을 수 있다.
- omit : 모든 요청에 인증 정보를 담지 않는다.
만약 same-origin이나 include 같은 옵션을 사용하여 리소스 요청에 인증 정보가 포함되면, Cross-Origin 리소스를 요청할 때 단순히 access-control-allow-origin만 확인하는 것이 아니라, 다른 검사 조건들이 추가된다.
만약 서버 응답에 access-control-allow-credentials가 true로 설정되어 있지 않거나, access-control-allow-origin 헤더에 있는 값이 허용된 출처가 아니라면 오류가 발생하게 된다.
참고:
https://ko.wikipedia.org/wiki/%EA%B5%90%EC%B0%A8_%EC%B6%9C%EC%B2%98_%EB%A6%AC%EC%86%8C%EC%8A%A4_%EA%B3%B5%EC%9C%A0
https://escapefromcoding.tistory.com/724
https://velog.io/@dbrrnjs9/CORS-%ED%95%84%ED%84%B0Cross-Origin-Resource-Sharing-Filter
https://www.baeldung.com/cs/cors-preflight-requests
https://hymndev.tistory.com/78
https://hannut91.github.io/blogs/infra/cors