[HTTP] CORS(Cross-Origin Resource Sharing) 이란
Cross-Origin Resource Sharing 은 추가 HTTP 헤더를 사용하여 브라우저가 한 출처에서 실행중인 웹 애플리케이션에 선택된 액세스 권한을 부여하도록하는 메커니즘입니다.
CORS 를 알기전에 same-origin policy 에 대해 알아야합니다.
Same-Origin Policy
same-origin policy 는 우리나라말로 동일 출처 정책으로, 어떤 출처에서 불러온 문서나 스크립트가 다른 출처에서 가져온 리소스와 상호작용하는 것을 제한하는 중요한 보안 방식입니다. same-origin policy 는 malware 나 위협적인 접근에 대한 문서를 분리해, 공격받을 수 있는 경로를 줄입니다.
same-origin 의 정의로는 두 URL 의 프로토콜, 포트(명시한 경우), 호스트가 모두 같아야 same-origin
이라고 말합니다.
즉, web application 은 본인으로부터 다른 origin (domain, protocol or port) 의 요청을 받았을 때 cross-origin HTTP request 를 실행합니다.
아래 표는 URL http://store.company.com/dir/page.html 의 origin 을 비교한 예시입니다.
URL | 결과 | 이유 |
---|---|---|
http://store.company.com/dir2/other.html | 성공 | 경로만 다름 |
http://store.company.com/dir/another.html | 성공 | 경로만 다름 |
https://store.company.com/secure.html | 실패 | 프로토콜 다름 |
http://store.company.com:81/dir/other.html | 실패 | 포트 다름 |
http://news.company.com/dir2/other.html | 실패 | 호스트 다름 |
브라우저들은 스크립트 내에서 초기화되는 cross-origin HTTP 요청을 제한합니다. 예를 들어, XMLHttpRequest
와 Fecth API 는 위에서 설명한 same-origin policy 를 따르기 때문에 다른 API 의 응답에 올바른 CORS 헤더가 포함되어 있지 않으면 해당 API 를 사용하는 웹 응용 프로그램은 응용 프로그램이 로드된 same-origin 의 resource 만 요청할 수 있습니다.
따라서, Spring Boot 내에서도 특정한 설정이 되어있지 않다면 CORS 오류가 발생하여 block 이 될 수 있기 때문에 same-origin 으로 통신하지 않는다면 CorsConfiguration
를 통해 cors 설정을 해주어야 합니다.
하지만, 모든 요청이 same-origin policy 를 따르는 것은 아닙니다.
CORS 요청의 종류로는
- Simple Request
- Preflight Request
- Credential Request
로 총 3가지가 존재합니다.
순서대로 설명드리도록 하겠습니다.
Simple Request
특정 request 는 CORS preflight
를 trigger 하지 않습니다. 이러한 것을 simple requests
라고 합니다.
simple request
란 다음의 조건을 모두 충족시키는 request 입니다.
- 허용되는 Method
- GET
- HEAD
- POST
- headers 를 자동으로 user agent 로 부터 분리시킵니다. (예를들어, Connection, User-Agent, 헤더에 정의된 "forbidden header name") 다음과 같은 Fetch spec 에 정의되어있는
CORS-safelisted request-header
헤더들만 허락해줍니다.- 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
XMLHttpRequestUpload
에 등록된 request 의 event listeners 객체는 모두 허용되지 않습니다.XMLHttpRequest.upload
property 를 사용하여 접근할 수 있습니다.- ReadableStream 은 request 에 사용할 수 없습니다.
이러한 종류의 cross-site request 는 이미 web content 에서 요청할 수 있습니다.
만약 그 server 가 적절하지 않은 헤더를 보낸다면 requester 에게 response data 를 보내지 않을겁니다.
그러므로, 위조된 cross-site request 을 막는 site 는 새로운 HTTP access control 에 대한 걱정할 필요가 없습니다.
다음은 simple request 요청에 대한 간단한 예시입니다.
GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://foo.example
request header 의 Origin
을 보시면, https://foo.example 에서 오는것을 알 수 있습니다.
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
[…XML Data…]
위의 응답에서, server는 Access-Control-Allow-Origin
헤더를 다시 보냈습니다. Origin
헤더의 사용과 Access-Control-Allow-Origin
의 사용은 acces control protocol 을 가장 간단하게 사용했다는 것을 보여줍니다.
또한, Access-Control-Allow-Origin: *
은 어떤 domain 이든지 resource 를 허락한다는 것을 의미합니다.
만약, 특정 resource 의 owners 에게만 허락하고 싶으면 다음과 같이 정의하시면 됩니다.
Access-Control-Allow-Origin: https://foo.example
Preflighted request
simple requeest
가 아니라면 preflighted request
가 실질적인 request 이 안전한지에 대한것을 결정하기 위해 OPTIONS
method 를 사용하여 특정 domain 으로 HTTP request 를 우선 던집니다.
Preflight Request 는 OPTIONS Method 를 통해 한번 확인을 하기 떄문에 simple request 에 비하여 까다롭지 않습니다.
다음과 같은 조건을 가집니다.- 모든 Method 를 허용합니다.
- application/json 이나 application/xml 등 다른 Content-type 으로 요청을 보낼 수 있습니다.
- Custom header 를 사용할 수 있습니다.
간단한 예시를 들어보겠습니다.
OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
HTTP/1.1 204 No Content
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
일단 위와 같은 OPTIONS 의 Method 로 preflight request 가 성공하면, 신뢰한다고 가정하여 다음과 같은 실제 request 를 보냅니다.
POST /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: https://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: https://foo.example
Pragma: no-cache
Cache-Control: no-cache
<person><name>Arun</name></person>
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain
[Some GZIP'd payload]
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
Access-Control-Request-Method
헤더는 server 의 preflight request 부분인 실제 request 가 보내졌음을 나타내며 실제 POST Method request 입니다.
Access-Control-Request-Headers
는 server 가 실제 request 를 보낼 때 X-PINGOTHER
와 Content-Type
의 Custom Headers 와 함께 보낸다고 알리는 것입니다.
Access-Control-Allow-Origin: [http://foo.example
Access-Control-Allow-Methods](http://foo.exampleAccess-Control-Allow-Methods): POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Server 는 Access-Control-Allow-Methods
에 POST
와 GET
이 가능한 Method 라고 헤더에 포함시켰습니다.
이러한 header 는 Allow
response header 와 유사하지만, access control 의 context 에서는 엄격하게 사용해야 합니다.
server 는 또한 Access-Conrol-Allow-Headers
를 X-PINGOTHER, Content-Type
와 함께 보내며 이러한 실제 request 를 승인한다는 것을 나타내고 있습니다.
마지막으로, Access-Control-Max-Age
는 다른 preflight request 없이 얼마나 오래 해당 preflight request 에 대한 response cashe 할 수 있는지에 대한 두번째 값입니다.
위에 예시에서는, 86400 초인 24시간을 cashe 합니다.
또한, 각각의 browser 마다 최대 interval value 를 우선 가지고 있습니다.
대부분은 preflighted request
후에 redirect
를 동시에 지원을 하고 있지 않습니다.
만약, preflighted request
후에 redirect
가 발생하면, 대부분의 browser 는 다음과 같은 error message
를 보여줄 것입니다.
The request was redirected to 'https://example.com/foo', which is disallowed for cross-origin requests that require preflight
Request requires preflight, which is disallowed to follow cross-origin redirect
CORS protocol 은 기본적으로 지원을 했었는데, 추후에 지원하지 않도록 변경되었습니다.
그러나, 모든 브라우저가 그런 변경에대해 적용한 것은 아니며 기존의 방식을 사용하는 브라우저도 존재합니다.
따라서, 모든 브라우저가 표준이 되기전까지
Requests with credentials
Credential Request
는 XMLHttpRequest
를 통해 request 시 HTTP cookies
와 HTTP Authentication
를 인지하도록 해주는 것입니다.
기본설정으로는, browsers 가 credentials 를 보내지 않습니다. 따라서 withCredentials
를 true 를 설정하여 credentials request
를 요청할 수 있습니다.
아래는 credentials request 의 간단한 예시입니다.
GET /resources/access-control-with-credentials/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Referer: http://foo.example/examples/credential.html
Origin: http://foo.example
Cookie: pageAccess=2
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:34:52 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 106
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
[text/plain payload]
위 내용을 보시면 10번째 라인에 Cookie 가 정의되어 있지만, 만약 bar.orhter 가 Access-Control-Allow-Credentials: true
의 헤더와 함께 response 하지 않으면 그 response 는 무시되고 web content 를 이용할 수 없게 만들어집니다.
credentialed request를 response 할 때,
*
대신에 서버는Access-Control-Allow-Origin header
에 특정origin
을 꼭 명시해줘야 합니다.그 이유는,
Cookie
header 를 포함한 request 가 만약 header 에Access-Control-Allow-Origin
이*
라면 실패할 것이기 때문입니다.
총 3가지의 Requeest 요청에 대해 알아보았습니다.
처음 웹개발을 진행할 때 CORS 에 대해 알지 못해, front 에서 api 서버로 통신이 되지 않아 몇일간 해맸던 기억이 있습니다.
그 당시에는 이런 정보를 알지 못해 다른 것들만 이것저것 수정하다가 CORS 가 원인인 것을 알고 중요성을 느껴 찾아봤던 기억을 토대로 작성하였습니다.
만약 CORS 관련하여 서버에 오류가 발생한다면 ( OPTIONS Method
에 대한 오류나, XMLHttpRequest
관련 오류) 위의 해당 내용을 숙지하여 개발하면 쉽게 해결하실 수 있을 겁니다.
CORS
는 Cross-Origin
문제를 해결하는 방법이기도 하지만, 보안적
으로도 가져가야할 규약이기도합니다.
따라서 신중한 선택과 고민을 통해 HTTP hedaer
와 허용 가능한 Method
를 선택할 필요가 있습니다.
REFRENCE
- https://homoefficio.github.io/2015/07/21/Cross-Origin-Resource-Sharing/
- https://developer.mozilla.org/ko/docs/Web/HTTP/Access_control_CORS
- https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy