CORS는 웹 브라우저에서 외부 도메인 서버와 통신하기 위한 방식을 표준화한 스팩입니다. 서버와 클라이언트가 정해진 헤더를 통해 서로 요청이나 응답에 반응할지 결정하는 방식으로 CORS(cross-origin resource sharing, 교차 출처 자원 공유)라는 이름으로 표준화 되었습니다.
SOP의 문제점을 해결하기 위한 정책인 만큼 CORS 방식을 통해 Cross-Origin에서도 Cross-Site HTTP Request를 허용하도록 설정할 수 있습니다.
CORS Request는 다음과 같이 4가지 종류가 있습니다.
CORS Simple Request는 GET, POST, HEAD method를 사용합니다.
특히 POST method를 사용할 경우 Content-type은 application/x-www-form-urlencoded, multipart/form-data, text/plain를 사용해야합니다.
또한 custom header를 사용해서는 안됩니다.
아래의 예에서는 Origin header에 https://foo.example 도메인을 작성하여 해당 도메인에서 리소스에 접근 가능여부를 확인합니다.
서버의 응답으로 Access-Control-Allow-Origin header:*을 전달합니다.
*
은 해당 서버는 모든 도메인에서 리소스에 접근 가능하다는 것을 의미합니다.
GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0
Accept: text/html,application/xhtml+xml,application/
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
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…]
다음 예는 서버측에서 Access-Control-Allow-Origin header 에서 특정 도메인만 리소스에 접근할 수 있도록 설정하는 예시입니다. 해당 예는 이후 알아볼 Credential Request 입니다.
서버 측에서 Access-Control-Allow-Origin에 설정된 도메인이 Request Origin Header을 통해 전달되면 Response Access-Control-Allow-Origin Header로 다시 전달됩니다.
GET /accountDetails HTTP/1.1
Host: 9900c2000a.web-security-academy.net
Origin: https://9900c2000a.web-security-academy.net
Connection: close
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Sec-Fetch-Dest: empty
Accept: */*
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Referer: https://9900c2000a.web-security-academy.net/my-account?id=wiener
Accept-Encoding: gzip, deflate
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: session=sbtUVKYJx1YFMT2KaPW5zqCu0yZG1Csw
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://9900c2000a.web-security-academy.net
Access-Control-Allow-Credentials: true
Content-Type: application/json; charset=utf-8
X-XSS-Protection: 0
Connection: close
Content-Length: 89
{
"username": "",
"email": "",
"apikey": ""
}
만약 Access-Control-Allow-Origin 이 설정되지 않은 도메인이 Request Origin Header을 통해 전달되면 Response Access-Control-Allow-Origin Header가 전달되지 않습니다.
GET /accountDetails HTTP/1.1
Host: 9900c2000a.web-security-academy.net
Origin: https://test.com
Connection: close
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Sec-Fetch-Dest: empty
Accept: */*
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Referer: https://9900c2000a.web-security-academy.net/my-account?id=wiener
Accept-Encoding: gzip, deflate
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: session=sbtUVKYJx1YFMT2KaPW5zqCu0yZG1Csw
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Content-Type: application/json; charset=utf-8
X-XSS-Protection: 0
Connection: close
Content-Length: 89
{
"username": "",
"email": "",
"apikey": ""
}
CORS Request를 통한 Cross-Site Request는 사용자 데이터에 영향을 미칠 수 있기 때문에 Actual Request(실제 요청)이 안전한지 판단하기 위해서 Preflight Request(OPTIONS method)를 먼저 전달합니다.
Preflighted Request는 Actual Request(실제요청) 시 사용하려는 method, header를 전달하고, 서버는 Response로 허용 가능한 method, header를 전달해줍니다.
만약 Preflighted Request로 전달한 method, header가 Response로 받은 허용 가능한 method, header 목록에 포함되지 않는다면 브라우저는 405 Method Not Allowed를 발생시켜서 Actual Request를 하지 않도록 합니다.
CORS Preflight Request는 먼저 OPTIONS method를 통해 다음 3가지의 HTTP Header를 전달합니다.
요청을 받은 서버는 Response로 다음 HTTP Header를 전달합니다.
디음 예제에서는 OPTIONS method를 통해 서버가 POST method, X-PINGOTHER header, Content-Type header를 허용하는지 확인합니다.
Response로 받은 허용 가능한 method, header가 포함되어 있고, Access-Control-Allow-Origin 에도 요청하는 도메인 주소가 포함되어 있으므로 정상적으로 CORS Request를 할 수 있습니다.
OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9
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
POST /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.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]
표준 CORS는 기본적으로 Request 시 cookie를 전송하지 않습니다.
CORS Request 시 cookie를 통해서 자격증명을 해야하는 경우, 또는 cookie를 Request에 포함하고 싶다면 XMLHttpRequest 객체의 withCredentials 속성 값을 true로 설정해야합니다.
서버는 Access-Control-Allow-Credentials: true 와 Set-Cookie Header에 다음 Request 시 전달해야하는 cookie 값을 Response에 포함시켜 전달합니다.
다음 예제는 Credential Request입니다.
Response 값에 Access-Control-Allow-Credentials: true, Set-Cookie: pageAccess=1을 포함하여 전달합니다.
GET /resources/access-control-with-credentials/ HTTP/1.1
Host: aruner.net
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Accept: */*
Origin: http://arunranga.com
Referer: http://arunranga.com/examples/access-control
Accept-Encoding: gzip, deflate
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7,ja;q=0.6
Connection: close
HTTP/1.1 200 OK
Date: Thu, 09 Apr 2020 05:40:53 GMT
Server: Apache
Access-Control-Allow-Origin: http://arunranga.com
Cache-Control: no-cache
Pragma: no-cache
Access-Control-Allow-Credentials: true
Set-Cookie: pageAccess=1; expires=Sat, Max-Age=2592000
Upgrade: h2
Connection: Upgrade, close
Cache-Control: max-age=172800
Expires: Sat, 11 Apr 2020 05:40:53 GMT
Vary: Accept-Encoding,User-Agent
Content-Length: 80
Content-Type: text/plain;charset=UTF-8
I do not know you or anyone like you
Request 시 Cookie: pageAccess=1 을 포함하여 전달합니다.
서버는 Response로 다음 Request 시 전달해야하는 Set-Cookie: pageAccess=2 값을 포함하여 전달합니다.
GET /resources/access-control-with-credentials/ HTTP/1.1
Host: aruner.net
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Accept: */*
Origin: http://arunranga.com
Referer: http://arunranga.com/examples/access-control
Accept-Encoding: gzip, deflate
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7,ja;q=0.6
Cookie: pageAccess=1
Connection: close
HTTP/1.1 200 OK
Date: Thu, 09 Apr 2020 05:41:38 GMT
Server: Apache
Access-Control-Allow-Origin: http://arunranga.com
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: pageAccess=2; expires=Sat, Max-Age=2592000
Upgrade: h2
Connection: Upgrade, close
Cache-Control: max-age=172800
Expires: Sat, 11 Apr 2020 05:41:38 GMT
Vary: Accept-Encoding,User-Agent
Content-Length: 104
Content-Type: text/plain;charset=UTF-8
You have been to aruner.net at least 1 time(s) before!
기본적으로 withCredentials 속성 값은 false 입니다. Credential Rquest와 같이 별도로 지정을 하지 않는 모든 요청은 Non-Credential Request 입니다.
따라서 Request 시 cookie를 전달하지 않습니다.
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
https://java119.tistory.com/67
https://brunch.co.kr/@adrenalinee31/1
https://www.popit.kr/cors-preflight-인증-처리-관련-삽질
http://wiki.gurubee.net/display/SWDEV/CORS+(Cross-Origin+Resource+Sharing)
http://arunranga.com/examples/access-control/