CORS (Cross-Origin Resource Sharing)
내가 허용한 오리진만 허용한다.
차음 전송되는 리소스의 도메인과 다른 도메인으로부터 리소스가 요청될 경우 해당 리소스는 cross-origin HTTP 요청에 의해 요청된다.
예를 들어, http://domain-a.com 으로부터 전송되는 HTML 페이지가 src 속성을 통해 http://domain-b.com/image.jpg 를 요청하는 경우가 있다.
오늘날 많은 웹 페이지들은 CSS 스타일시트, 이미지, 그리고 스크립트와 같은 리소스들을 각각의 출처로부터 읽어온다.
한 도메인에서 로드되어 다른 도메인에 있는 리소스와 상호작용하는 클라이언트 웹 에플리케이션에 대한 방법을 정의
도메인이나 포트 프토로콜이 다른 서버에 리소스를 요청하는 것
브라우저에서 보안 상의 이유로 스크립트에서 시작한 교차 출처 HTTP 요청을 제한한다고 한다.
즉 이 API를 사용하는 웹 애플리케이션은 자신의 출처와 동일한 리소스만 불러올 수 있는 것인데 이것을 동일 출처 정책(Same-Origin Policy)
고로 다른 출처의 리소스를 불러오려면 그 출처에서 올바른 CORS 헤더를 포함한 응답을 반환해야 한다.
웹 서버를 통해서 클라이언트 브라우저의 앱(웹 어플리케이션)의 사용자를 보호하기 위한 브라우저 만의 자발적인 보안 조치(그러지 않을 수도 있다)
SOP(Same-Origin Policy)
기본적으로 보안상의 이유로 브라우저는 Same-Origin Policy(SOP, 동일 출처 정책)을 따른다.
그렇기 때문에 XMLHttpRequest
와 fetch API
는 동일 출처 정책에 따라 동일한 출처의 리소스만을 요청할 수 있다.
하지만 웹 어플리케이션을 구현하다보면, 다른 출처에 있는 리소스를 요청하는 경우가 많다.
일례로, SPA(Single Page Application)로 구현된 웹 어플리케이션에서 다른 도메인을 가진 API 서버에 요청하는 경우는 매우 비일비재하다.
또한, web font을 요청하는 경우도 마찬가지이다.
cross-origin 요청을 필요한 경우가 많아지고 있기 때문에, 이를 안전하게 처리할 정책이 필요했기에 CORS가 나오게 됬다.
CORS는 cross-origin 요청을제한적으로 허용함으로써 좀 더 안전하게 cross-origin 요청을 처리할 수 있는 정책이다.
Simple Request
-> Preflight 를 트리거하지 않는 일부 요청
Preflight Request
-> 사전 전송, 안전한지 확인
Credential Request
-> 인증정보를 포함한 요청
1) 브라우저 : 요청 헤더에 Origin 이라는 필드에 요청을 보내는 출처를 함께 담아 보냄Origin : https://velog.io/
2) 서버 : 해당 요청에 대한 응답시 응답 헤더의 Access-Control-Allow-Origin 이라는 필드에 리소스를 접근하는 것이 허용된 출처를 내려줌
3) 브라우저 : 보냈던 요청의 Origin과 서버가 보내준 응답의 Access-Control-Allow-Origin를 비교하여 유효성 체크
CORS 요청이 온다면, 서버는 Access-Control-* 헤더를 메시지에 포함시킬 수 있다.
Access-Control-Allow-Origin
: 요청을 허용할 출처를 명시할 때 사용하며, *
을 사용하면 모든 출처의 리소스 요청을 허용합니다.Access-Control-Allow-Methods
: 어떤 메서드를 허용할 것인지 명시합니다.Access-Control-Allow-Headers
: 어떤 헤더들을 허용할 것인지 명시합니다.Access-Control-Max-Age
: preflight 요청에 대한 응답을 브라우저에서 얼마만큼 캐싱하고 있을지 설정할 때 사용합니다.Access-Control-Expose-Headers
: 브라우저가 스크립트에 노출시킬 헤더의 목록을 명시할 때 사용합니다. 기본적으로 다음 7가지 헤더(일명 CORS safe-listed header)는 따로 설정하지 않아도 노출시킵니다.Cache-Control
Content-Language
Content-Length
Content-Type
Expires
Last-Modified
Pragma
서버는 위와 같은 헤더를 통해 리소스에 대한 CORS 요청을 어느 수준까지 허용할 것인지 클라이언트(브라우저)에게 알려주고, 이를 바탕으로 브라우저는 추후동작을 결정한다.
CORS 요청을 보낼 때, 브라우저는 해당 요청을 두 가지 방식으로 처리한다.
Preflight request
를 사용한 방식과 그렇지 않은 방식(Simple request
)이다.
Simple request
다음과 같은 조건을 만족하면, 브라우저는 CORS 요청을 Simple request
로 처리한다.
GET
, POST
, HEAD
Content-Type
헤더 값으로 다음과 같은 값을 사용하였을 때application/x-www-form-urlencoded
multipart/form-data
text/plain
XMLHttpRequestUpload
객체에 이벤트 리스너가 등록되지 않은 경우ReadableStream
객체를 사용하지 않은 경우이 방식은 일반 요청이랑 동일하게 동작하며, 단 하가지 다른 점은 응답의 Access-Control-Allow-Origin
에 따라 브라우저가 응답을 정상적으로 처리할지를 결정한다.
에를 들어 http://localhost:8080
에서 서버(http://localhost:9000/api/menus
)에 요청을 보낸다면, 다음과 같은 요청 메시지가 전송된다.
간단하게 Origin 헤더만 추가해서 보내준다.
GET /api/menus HTTP/1.1
Host: localhost:9000
Referer: http://localhost:8080/
Origin: http://localhost:8080
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
...
아래 응답에 Access-Control-Allow-Origin 헤더가 존재하지 않기 때문에 브라우저는 이 응답을 자바스크립트 코드에 노출시키지 않고 오류를 뱉어낸다.
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 155
...
// 교차 출처 요청 차단: 동일 출저 정책으로 인해 http://localhost:9000/api/menus 에 있는
// 원격 자원을 차단하였습니다.
// 원인: 'Access-Control-Allow-Origin' CORS 헤더가 없음.
서버에 Access-Control-Allow-Origin 헤더를 추가해서 응답하면 다음과 같이 정상적으로 자바스크립트 코드에서 응답을 처리할 수 있다.
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:8080 // 이 부분
Content-Type: application/json; charset=utf-8
Content-Length: 155
...
Preflight request
PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH
Simple request
가 아니면 브라우저는 실제 요청을 보내기 전에 한 가지 작업을 수행한다.
Preflight request
를 실제 요청 메시지보다 서버에 먼저 보낸다.
서버는 이에 대한 응답을 주고, 브라우저는 이를 통해 실제 요청을 보낼지 결정한다.
Preflight request
는 OPTIONS
메서드를 사용하며, 다음과 같은 요청 헤더를 사용한다.
Access-Control-Request-Method
: 실제 요청에서 사용하는 메서드를 서버가 알 수 있도록
설정하는 헤더
Access-Control-Request-Headers
: 실제 요청에 포함될 헤더를 서버가 알 수 있도록
설정하는 헤더
Origin
: 요청을 보낸 출처로, URL 중 scheme과 host, port만 명시
보통 Preflight request
는 브라우저가 자동적으로 생성하기 때문에 크게 신경 쓸 필요는 없지만, 각 헤더가 무엇을 의미하는지는 알아둘 필요가 있다.
응답이 오면, 브라우저는 방금 언급한 3가지 헤더 값과 응답의 Access-Control-*
헤더의 값을 비교하며 실제 요청을 서버에게 보낼지 결정한다.
예를 들어, 다음과 같은 요청을 보낸다면 Simple request
의 조건을 만족하지 못하므로, 실제 요청을 보내기 전에 Preflight request
를 보낸다.
OPTIONS /api/menus HTTP/1.1
Host: localhost:9000
Access-Control-Request-Method: GET
Access-Control-Request-Headers: content-type
Referer: http://localhost:8080/
Origin: http://localhost:8080
...
서버가 다음과 같이 응답을 했다면, 브라우저는 실제 요청을 보내도 문제가 없다고 판단하여 실제 요청을 서버에 보낸다. 이후 과정은 일반적인 요청-응답 사이클과 같다.
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:8080
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: -1
Connection: keep-alive
Content-Length: 0
GET /api/menus HTTP/1.1
Host: localhost:9000
Referer: http://localhost:8080/
Content-Type: application/json
Origin: http://localhost:8080
...
하지만 서버가 다음과 같은 응답을 했다면, 브라우저는 실제 요청을 보내지 않고 오류 메시지를 뱉어낼 것이다.
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:8080
Access-Control-Max-Age: -1
Connection: keep-alive
Content-Length: 0
// 교차 출처 요청 차단: 동일 출처 정책으로 인해 http://localhost:9000/api/menus 에 있는
// 원격 자원을 차단하였습니다.
// 원인: CORS 사전 점검 응답의 헤더 'Access-Control-Allow-Headers'에 따라
// 헤더 'Content-Type'가 허용되지 않음
// 교차 출처 요청 차단: 동일 출처 정책으로 인해 http://localhost:9000/api/menus 에 있는
// 원격 자원을 차단하였습니다.
// 원인: CORS 요청이 성공하지 못함
Credential request
Access-Control-Allow-Origin
에는 *를 사용할 수 없으며, 명시적인 URL이어야한다.2) 응답 헤더에는 반드시 Access-Control-Allow-Credentials: true
가 존재해야한다.브라우저가 자발적으로 simple request가 아닌 이상 preflight request를 서버에 날린다.
허용이 되면 그다음으로 실제 원랬던 request가 날라간다.
reponse를 받아서 이제 onload() 나 onerror()를 확인
이게 CORS의 핵심