๐ ์ ๋ช ๋์ CORS ๊ฐ๋ & ํด๊ฒฐ๋ฒ - ์ ๋ฆฌ ๋ํ์ ๐
์ ๋ช ๋์ CORS ์๋ฌ ๋ฉ์ธ์ง
์น ๊ฐ๋ฐ์ ํ๋ค๋ณด๋ฉด ๋ฐ๋์ ๋ง์ฃผ์น๋ ๋ฉ๋ฉ ๊ฐ์ ์๋ฌ๊ฐ ๋ฐ๋ก CORS ์ด๋ค. ์น ๊ฐ๋ฐ์ ์ ์ ์ ๊ณ ์์ด๋ผ๊ณ ํ ์ ๋๋ก, CORS๋ ๋๊ตฌ๋ ํ ๋ฒ ์ ๋๋ ๊ฒช๊ฒ ๋๋ค๊ณ ํด๋ ๊ณผ์ธ์ด ์๋๋ค.
ํ๋ก ํธ์๋ ๊ฐ๋ฐ์ ์ ์ฅ์์ ์์ฒญ ์ฝ๋๋ฅผ ์ด์ํ๊ฒ ์ ์๊ฒ๋ ์๋๊ณ , ๋ฐฑ์๋ ๊ฐ๋ฐ์ ์ ์ฅ์์ ์๋ฒ ์ฝ๋๋ ์ธํ ์ด ์ด์ํ๊ฒ๋ ์๋๋ค. ๋ชจ๋ ๊ฒ ๋ฉ์ฉกํ๋ฐ ์ ์์ฒญํ ์๋ฃ์ ๋ํ ์๋ต์ ์๋ป๊ฑด ์๋ฌ์ค๋ก ํ๋ตํ๋๊ฒ ๋ฌธ์ ์ด๋ค. ๐คฌ
์ด๋ฌํ ํ์์ด ์ผ์ด๋๋ ์ด์ ๋, ์น ๋ธ๋ผ์ฐ์ ๋ HTTP ์์ฒญ์ ๋ํด์ ์ด๋ค ์์ฒญ์ ํ๋๋์ ๋ฐ๋ผ ๊ฐ๊ธฐ ๋ค๋ฅธ ํน์ง์ ๊ฐ์ง๊ณ ์๊ธฐ ๋๋ฌธ์ด๋ค.
์์ฒญ ๋ฐฉ์์ ๋ฐ๋ผ ๋ค๋ฅธ CORS ๋ฐ์ ์ฌ๋ถ
1. <img>, <video>, <script>, <link> ํ๊ทธ ๋ฑ
→ ๊ธฐ๋ณธ์ ์ผ๋ก Cross-Origin ์ ์ฑ ์ ์ง์ํจ
<link>ํ๊ทธ์ href ์์ ๋ค๋ฅธ ์ฌ์ดํธ์ .css ๋ฆฌ์์ค์ ์ ๊ทผํ๋ ๊ฒ์ด ๊ฐ๋ฅ<img>ํ๊ทธ์ src ์์ ๋ค๋ฅธ ์ฌ์ดํธ์ .png, .jpg ๋ฑ์ ๋ฆฌ์์ค์ ์ ๊ทผํ๋ ๊ฒ์ด ๊ฐ๋ฅ<script>ํ๊ทธ์ src ์์ ๋ค๋ฅธ ์ฌ์ดํธ์ .js ๋ฆฌ์์ค์ ์ ๊ทผํ๋ ๊ฒ์ด ๊ฐ๋ฅ (type="module"์์ฑ์ ์ ์ธ)
<link rel="stylesheet" href="…" />
<script src="…"></script>
<img src="…" />
2. XMLHttpRequest, Fetch API ์คํฌ๋ฆฝํธ
→ ๊ธฐ๋ณธ์ ์ผ๋ก Same-Origin ์ ์ฑ ์ ๋ฐ๋ฆ
- ๋ค๋ฅธ ๋๋ฉ์ธ์ ์์ค์ ๋ํด ์๋ฐ์คํฌ๋ฆฝํธ ajax ์์ฒญ API ํธ์ถ์
- ์น ํฐํธ CSS ํ์ผ ๋ด @font-face์์ ๋ค๋ฅธ ๋๋ฉ์ธ์ ํฐํธ ์ฌ์ฉ ์
์๋ฐ์คํฌ๋ฆฝํธ์์์ ์์ฒญ์ ๊ธฐ๋ณธ์ ์ผ๋ก ์๋ก ๋ค๋ฅธ ๋๋ฉ์ธ์ ๋ํ ์์ฒญ์ ๋ณด์์ ์ ํํ๋ค. ๋ธ๋ผ์ฐ์ ๋ ๊ธฐ๋ณธ์ผ๋ก ํ๋์ ์๋ฒ ์ฐ๊ฒฐ๋ง ํ์ฉ๋๋๋ก ์ค์ ๋์ด ์๊ธฐ ๋๋ฌธ์ด๋ค. (์ฃผ๋ก ์์ ์ ์๋ฒ)
์ฒ์ ๋ณด๋ ์ฉ์ด๊ฐ ๋์จ๋ค. Same Origin ์ ์ฑ ๊ณผ Cross Origin ์ ์ฑ ์ด๋ ๋์ฒด ๋ฌด์จ ์ ์ฑ (Policy)์ ๋งํ๋ ๊ฒ์ผ๊น? ์ด๋ฌํ ์ ์ฑ ๋ค์ด ๋ญ๊ธธ๋ ์น ๋ธ๋ผ์ฐ์ ๊ฐ ์ธ๋ถ ๋ฆฌ์์ค๋ฅผ ๊ฐ๋ ค์ ๋ฐ๋ ๊ฒ์ผ๊น?
๋ฐ๋ก ์ด Same Origin / Cross Origin ์ ์ฑ ์ ์ ๋ณด ๋ถ์กฑ์ผ๋ก ์ธํด ๋๋๋ชจ๋ฅด๊ฒ ์ ์ฑ ์ ์๋ฐํ๋ ํ๋์ ํ๊ฒ ๋์ด CORS ์๋ฌ๊ฐ ๋ํ๋๋ ๊ฒ์ด๋ค.
์์ฒญ ๋ฐฉ์์ ๋ฐ๋ผ ๋ค๋ฅธ CORS ๋ฐ์ ์ฌ๋ถ๋ฅผ ์ข ๋ ์ดํดํ๊ธฐ ์ฝ๊ฒ ์๋ html ์ฝ๋๋ฅผ ์ง์ ์์ฑํ๊ณ ํ
์คํธ ํด๋ณด์. ๋๊ฐ์ ์๋ฒ ๋๋ฉ์ธ์ผ๋ก ๋ถํฐ check.svg ์ด๋ฏธ์ง๋ฅผ ๊ฐ์ ธ์ค๋๋ฐ, ๊ฐ๊ฐ <img> ํ๊ทธ์ src ์์ฑ์ผ๋ก ๊ฐ์ ธ์ค๋ ๋ฐฉ์๊ณผ ์๋ฐ์คํฌ๋ฆฝํธ์์ ajax ์์ฒญ์ผ๋ก ๊ฐ์ ธ์ค๋ ๋ฐฉ์์ด๋ค.
<body>
<img src="https://third-party-test.glitch.me/check.svg" alt="์ด๋ฏธ์ง">
<script>
fetch('https://third-party-test.glitch.me/check.svg')
.then(response => response.blob())
.then(imgBlob => {
const imageObjectURL = URL.createObjectURL(imgBlob); // ์๋ต ๋ฐ์ ์ด๋ฏธ์ง๋ฅผ blob ๊ฐ์ฒด๋ก ๋ณํ
const img = document.createElement('img'); // ์ด๋ฏธ์ง ํ๊ทธ๋ฅผ ์์ฑํ๊ณ
img.src = imageObjectURL; // ์ด๋ฏธ์ง ๊ฒฝ๋ก๋ฅผ ์ค์ ํ๋ค
document.body.append(img); // html์ ์ถ๊ฐ
})
</script>
</body>
ํ์ด์ง ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด ์ด๋ฏธ์ง๊ฐ ํ๋๋ง ๋ํ๋๋ ๊ฑธ ๋ณผ ์ ์์ ๊ฒ์ด๋ค.
๋ธ๋ผ์ฐ์ ๊ฐ๋ฐ์ ๋๊ตฌ์ Network ์ฐฝ์ ๋ณด๋ฉด check.svg ์ด๋ฏธ์ง์ ๋ํด์ ๋๋ฒ ์์ฒญ์ ํ์ง๋ง ์๋ฐ์คํฌ๋ฆฝํธ fetch Type์ผ๋ก ์์ฒญํ ๊ฒ์ด Status๊ฐ CORS error ์์ ๋ณผ์๊ฐ ์๋ค.
์น ๊ฐ๋ฐ์ ํ๋ค๋ณด๋ฉด ๋ค๋ฅธ ๋๋ฉ์ธ ์๋ฒ์ ์๋ ์์์ ์ ๋ง ์์ฃผ ๊ฐ์ ธ๋ค ์ฐ๊ฑฐ๋ ํน์ ์ ๊ณตํด ์ค ํ ๋ฐ ์ด๋ฌํ ๊ธฐ๋ฐ ์ง์์ด ์๋ค๋ฉด ๋์ค์ ํฐ ๊ฑธ๋ฆผ๋์ด ๋๊ฒ ๋๋ค. ์ง๊ธ๋ถํฐ ์ด ์ง์ฆ๋๋ CORS๋ฅผ ๋๋ฒ ๋ค์ ๋ง์ฃผ์น์ง ์๋๋ก ์๋ฒฝํ๊ฒ ์ ๋ณตํ๋ฌ ๊ฐ๋ณด์ โ
CORS ์๋ฌ ํ๋ฐฉ ์ดํดํ๊ธฐ
CORS๋ ํจ์ถ ๋จ์ด๋ก์จ ์ด๋ฅผ ํ๋ฉด Cross-Origin Resource Sharing ์ด๋ผ๋ ๋จ์ด๋ก ์ด๋ฃจ์ด ์ ธ ์๋ค. ์ด ๋ฌธ์ฅ์ ์ง์ญํ๋ฉด "๊ต์ฐจ ์ถ์ฒ ๋ฆฌ์์ค ๊ณต์ ์ ์ฑ "์ด๋ผ๊ณ ํด์ํ ์ ์๋๋ฐ, ์ฌ๊ธฐ์ ๊ต์ฐจ ์ถ์ฒ๋ผ๊ณ ํ๋ ๊ฒ์ (์๊ฐ๋ฆฐ) ๋ค๋ฅธ ์ถ์ฒ๋ฅผ ์๋ฏธํ๋ ๊ฒ์ผ๋ก ๋ณด๋ฉด ๋๋ค.
CORS๊ฐ ๋ฌด์ผ ๋ปํ๋์ง ์์์ผ๋, ํ๋ฒ ์ฐ๋ฆฌ๋ฅผ ๊ดด๋กญํ๋ ์ ๋ช ๋์ CORS ์๋ฌ ๋ฉ์ธ์ง๋ฅผ ์ฐจ๊ทผ์ฐจ๊ทผ ํด์ํด๋ณด์.
Access to fetch at ‘https://myhompage.com’ 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.
'https://myhomepage.com'์์ 'https://localhost:3000' ์ถ์ฒ๋ก ๊ฐ์ ธ์ฌ ์ ์๋ ์ก์ธ์ค๊ฐ CORS ์ ์ฑ ์ ์ํด ์ฐจ๋จ๋์์ต๋๋ค. ์์ฒญ๋ ๋ฆฌ์์ค์ 'Access-Control-Allow-Origin' ํค๋๊ฐ ์์ต๋๋ค. ๋ถํฌ๋ช ํ ์๋ต์ด ํ์์ ์ ํฉํ ๊ฒฝ์ฐ, ์์ฒญ ๋ชจ๋๋ฅผ 'no-cors'๋ก ์ค์ ํ์ฌ CORS๊ฐ ๋นํ์ฑํ๋ ๋ฆฌ์์ค๋ฅผ ๊ฐ์ ธ์ค์ญ์์ค.
์๋ฌ ๋ฉ์ธ์ง๊ฐ ๋ถ์น์ ํ ๊ฒ์ ์๋์ง๋ง, ์๋ฌด๋๋ ๋ฐฐ๊ฒฝ์ง์์ด ๋ถ์กฑํ๋ค๋ณด๋ ์ด๋ป๊ฒ ํด๊ฒฐํด์ผ ํ ์ง ๊ฐ์ด ์์กํ๋ค.
์ฐ์ CORS์ ๋ค๋ฅธ ์ถ์ฒ ๋ฆฌ์์ค ๊ณต์ ์ ์ฑ (Cross Origin Resource Sharing)์ด ์ด๋ ํ ์ ์ฑ ์ธ์ง์ ๋ํด์ ์์๋ณผ ํ์์ฑ์ด ์์ ๊ฒ ๊ฐ๋ค. ๊ทธ ์ ์ ์๊น๋ถํฐ ์ถ์ฒ, ์ถ์ฒ ๊ฑฐ๋ฆฌ๋๋ฐ ์ด ์ถ์ฒ(Origin)์ด ๋ฌด์์ธ์ง ๊ฐ๋จํ๊ฒ ์ดํด๋ณด์.
์ถ์ฒ(Origin) ๋?
์ฐ๋ฆฌ๊ฐ ์ด๋ค ์ฌ์ดํธ๋ฅผ ์ ์ํ ๋ ์ธํฐ๋ท ์ฃผ์์ฐฝ์ ์ฐ๋ฆฌ๋ URL์ด๋ผ๋ ๋ฌธ์์ด์ ํตํด ์ ๊ทผํ๊ฒ ๋๋ค.
์ด์ฒ๋ผ URL์ https://domain.com:3000/user?query=name&page=1 ๊ณผ ๊ฐ์ด ํ๋์ ๋ฌธ์์ด ๊ฐ์ง๋ง, ์ฌ์ค์ ๋ค์๊ณผ ๊ฐ์ด ์ฌ๋ฌ๊ฐ์ ๊ตฌ์ฑ ์์๋ก ์ด๋ฃจ์ด์ ธ ์๋ค.
- Protocol(Scheme) : http, https
- Host : ์ฌ์ดํธ ๋๋ฉ์ธ
- Port : ํฌํธ ๋ฒํธ
- Path : ์ฌ์ดํธ ๋ด๋ถ ๊ฒฝ๋ก
- Query string : ์์ฒญ์ key์ value๊ฐ
- Fragment : ํด์ ํํฌ
๋ช๋ช ๋ ์๋ถ๋ค ์ค์ ์ด๋ฏธ ๊ฐ URL์ ์์ฑ๋ค์ ๋ํด ๋ค ์๊ณ ์๋ ์์ค์ด ๋์ ๋ถ๋ค๋ ์๊ณ , ์์ง์ ์์ธํ ์ ๋ชจ๋ฅด๋ ๋ถ๋ค๋ ๊ณ์ค๊ฑฐ๋ผ ์ถ์ธกํ๋ค.
CORS๋ฅผ ์ดํดํ๋๋ฐ ์์ด ์ ๊ฒ๋ค์ ๋ชจ๋ ์์์ผ ๋๋ ๊ฒ์ ์๋๊ณ , ๋ฑ 3๊ฐ์ง๋ง ๊ธฐ์ตํ๋ฉด ๋๋ค.
- Origin : Protocol + Host + Port
์ฆ, ์ถ์ฒ(Origin) ๋ผ๋ ๊ฒ์ Protolcol ๊ณผ Host ๊ทธ๋ฆฌ๊ณ Port ๊น์ง ๋ชจ๋ ํฉ์น URL์ ์๋ฏธํ๋ค๊ณ ๋ณด๋ฉด ๋๋ค. ๊ฐ๋จํ๊ฒ ์๋ฐ์คํฌ๋ฆฝํธ๋ก๋ ํ์ฌ ์ฌ์ดํธ์ Origin์ ์์๋ผ ์๋ ์๋ค.
console.log(location.origin); // "https://www.naver.com" (ํฌํธ ๋ฒํธ 80๋ฒ์ ์๋ต๋จ)
๋์ผ ์ถ์ฒ ์ ์ฑ (Same-Origin Policy)
์ถ์ฒ(Origin)์ ๋ํด์ ์์๋ดค์ผ๋ ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก Same Origin ์ ์ฑ ๊ณผ Cross Origin ์ ์ฑ ์ ๋ํด ์์๋ณด์.
๋จผ์ SOP(Same Origin Policy) ์ ์ฑ ์ ๋จ์ด ๊ทธ๋๋ก ๋์ผํ ์ถ์ฒ์ ๋ํ ์ ์ฑ ์ ๋งํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ด SOP ์ ์ฑ ์ '๋์ผํ ์ถ์ฒ์์๋ง ๋ฆฌ์์ค๋ฅผ ๊ณต์ ํ ์ ์๋ค.'๋ผ๋ ๋ฒ๋ฅ ์ ๊ฐ์ง๊ณ ์๋ค.
์ฆ, ๋์ผ ์ถ์ฒ(Same-Origin) ์๋ฒ์ ์๋ ๋ฆฌ์์ค๋ ์์ ๋ก์ด ๊ฐ์ ธ์ฌ์ ์์ง๋ง, ๋ค๋ฅธ ์ถ์ฒ(Cross-Origin) ์๋ฒ์ ์๋ ์ด๋ฏธ์ง๋ ์ ํ๋ธ ์์ ๊ฐ์ ๋ฆฌ์์ค๋ ์ํธ์์ฉ์ด ๋ถ๊ฐ๋ฅํ๋ค๋ ๋ง์ด๋ค.
๐ ๋์ผ ์ถ์ฒ ์ ์ฑ ์ด ํ์ํ ์ด์
๊ทธ๋ ๋ค๋ฉด ๋์ผ ์ถ์ฒ๊ฐ ์๋ ๊ฒฝ์ฐ ์ ๊ทผ์ ์ฐจ๋จํ๋ ์ด์ ๋ ๋ญ๊น?
์ฌ์ค ์ถ์ฒ๊ฐ ๋ค๋ฅธ ๋ ์ดํ๋ฆฌ์ผ์ด์ ์ด ์์ ๋ก์ด ์ํตํ ์ ์๋ ํ๊ฒฝ์ ๊ฝค ์ํํ ํ๊ฒฝ์ด๋ค. ๋ง์ผ ์ ์ฝ์ด ์๋ค๋ฉด, ํด์ปค๊ฐ CSRF(Cross-Site Request Forgery)๋ XSS(Cross-Site Scripting) ๋ฑ์ ๋ฐฉ๋ฒ์ ์ด์ฉํด์ ์ฐ๋ฆฌ๊ฐ ๋ง๋ ์ดํ๋ฆฌ์ผ์ด์ ์์ ํด์ปค๊ฐ ์ฌ์ด๋์ ์ฝ๋๊ฐ ์คํํ์ฌ ๊ฐ์ธ ์ ๋ณด๋ฅผ ๊ฐ๋ก์ฑ ์ ์๋ค.
๋ค์์ SOP ์ ์ฑ ์ด ์๋ ์ํฉ์์ ์ ์์ ์ธ ํํ์ด์ง์ ์ ์ํ๋ ์ํฉ์ ๊ฐ์ ํ ๊ฒ์ด๋ค.
- ์ฌ์ฉ์๊ฐ ์ ์ฑ ์ฌ์ดํธ์ ์ ์ํ๋ค.
- ์ด๋ ํด์ปค๊ฐ ๋ชฐ๋ ์ฌ์ด๋์ ์ ์์ ์ธ ์๋ฐ์คํฌ๋ฆฝํธ๊ฐ ์คํ๋์ด, ์ฌ์ฉ์๊ฐ ๋ชจ๋ฅด๋ ์ฌ์ด์ ์ด๋ ํฌํธ ์ฌ์ดํธ์ ์์ฒญ์ ๋ณด๋ธ๋ค.
- ๊ทธ๋ผ ํฌํธ ์ฌ์ดํธ์์ ํด๋น ๋ธ๋ผ์ฐ์ ์ ์ฟ ํค๋ฅผ ์ด์ฉํ์ฌ ๋ก๊ทธ์ธ์ ํ๊ฑฐ๋ ๋ฑ ์ํธ์์ฉ์ ๋ฐ๋ฅธ ๊ฐ์ธ ์ ๋ณด๋ฅผ ์๋ต ๊ฐ์ ๋ฐ์๋ค, ์ฌ์ดํธ์์ ํด์ปค ์๋ฒ(hacker.example.com)๋ก ์ฌ์ฐจ ๋ณด๋ธ๋ค.
- ์ด์ธ์๋ ์ฌ์ฉ์๊ฐ ์ ์์ค์ธ ๋ด๋ถ๋ง์ ์์ดํผ์ ํฌํธ๋ฅผ ๊ฐ์ ธ์ค๊ฑฐ๋, ํด์ปค๊ฐ ์ฌ์ฉ์ ๋ธ๋ผ์ฐ์ ๋ฅผ ํ๋ก์์ฒ๋ผ ์ ์ฉํ ์๋ ์๋ค.
๋ฐ๋ผ์ ์ด๋ฌํ ์ ์์ ์ธ ๊ฒฝ์ฐ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด, SOP ์ ์ฑ ์ผ๋ก ๋์ผํ์ง ์๋ ๋ค๋ฅธ ์ถ์ฒ์ ์คํฌ๋ฆฝํธ๊ฐ ์คํ๋์ง ์๋๋ก ๋ธ๋ผ์ฐ์ ์์ ์ฌ์ ์ ๋ฐฉ์งํ๋ ๊ฒ์ด๋ค.
๐ท๏ธ ๊ฐ์ ์ถ์ฒ์ ๋ค๋ฅธ ์ถ์ฒ ๊ตฌ๋ถ ๊ธฐ์ค
SOP ์ ์ฑ ์ ์ค์๋์ ํ์์ฑ์ ๋ํด ์์์ผ๋, ๋๊ฐ์ ์ถ์ฒ์ ๋ค๋ฆ ์ ๋ฌด๋ฅผ ํ๋จํ๋ ๊ธฐ์ค์ด ๋ฌด์์ธ์ง ์์๋ณด์.
์ถ์ฒ(Origin)์ ๋์ผํจ์ ๋ URL์ ๊ตฌ์ฑ ์์ ์ค Protocol(Scheme), Host, Port ์ด 3๊ฐ์ง๋ง ๋์ผํ๋ค๋ฉด ๋์ผ ์ถ์ฒ๋ก ํ๋จํ๋ค.
๋ค์์ https://www.domain.com:3000 ์ถ์ฒ์ ๋ํ ์ฌ๋ฌ URL์ ๋ฐ๋ฅธ ๋์ผ ์ถ์ฒ ๋น๊ต ํ ์ด๋ค.
URL | ๋์ผ ์ถ์ฒ ? | ์ด์ |
https://www.domain.com:3000/about | O | ํ๋กํ ์ฝ, ํธ์คํธ, ํฌํธ ๋ฒํธ ๋์ผ |
https://www.domain.com:3000/about?username=inpa | O | ํ๋กํ ์ฝ, ํธ์คํธ, ํฌํธ ๋ฒํธ ๋์ผ |
http://www.domain.com:3000 | X | ํ๋กํ ์ฝ ๋ค๋ฆ (http ≠ https) |
https://www.another.co.kr:3000 | X | ํธ์คํธ ๋ค๋ฆ |
https://www.domain.com:8888 | X | ํฌํธ ๋ฒํธ ๋ค๋ฆ |
https://www.domain.com | X | ํฌํธ ๋ฒํธ ๋ค๋ฆ (443 ≠ 3000) |
์ ๋ฆฌํ์๋ฉด ๊ฐ์ ํ๋กํ ์ฝ, ํธ์คํธ, ํฌํธ๋ฅผ ์ฌ์ฉํ๋ค๋ฉด, ๊ทธ ๋ค์ ๋ค๋ฅธ ์์๋ ๋ค๋ฅด๋๋ผ๋ ๊ฐ์ ์ถ์ฒ๋ก ์ธ์ ๋๋ค.
๋ฐ๋๋ก ํ๋กํ ์ฝ, ํธ์คํธ, ํฌํธ ์ค ํ๋๋ผ๋ ์์ ์ ์ถ์ฒ์ ๋ค๋ฅผ๊ฒฝ์ฐ ๋ธ๋ผ์ฐ์ ๋ ์ ์ฑ ์ ์ฐจ๋จํ๊ฒ ๋๋ค.
์น์ ํ์ญ์ฌ์ธ Internet Explorer ๋ธ๋ผ์ฐ์ ๋ ์๊ธฐ๊ฒ๋ ์ถ์ฒ ๋น๊ต์ Port ๋ถ๋ถ์ ๋ฌด์ํ๋ค. ์ด๋ ๊ณง ๋ณด์ ์ทจ์ฝ์ผ๋ก ์ด์ด์ง๋ฉฐ ์ ๊ทธ๋ ๊ฒ ์์ ์ป์ด๋จน๋์ง ์ ๋ํ ์ด์ ์ค์ ํ๋์ด๊ธฐ๋ ํ๋ค.
๐ ์ถ์ฒ ๋น๊ต์ ์ฐจ๋จ์ ๋ธ๋ผ์ฐ์ ๊ฐ ํ๋ค
์๋ด๊ธฐ ์น๊ฐ๋ฐ์๋ค์ด ์ฐฉ๊ฐํ๋ ๋ถ๋ถ์ด ์์ ์ถ์ฒ ๊ตฌ๋ถ์ ์๋ฒ๊ฐ ํ๋ ๊ฒ์ผ๋ก ์คํดํ๋ ๊ฒ์ด๋ค. ์๋ฌด๋๋ ์๋ฒ์ ์์ฒญ์ ํ๋๋ฐ ๋ฌด์ธ๊ฐ ์๋ฌ๊ฐ ๋จ๋ฉด ์๋ฒ๊ฐ ๋ฌธ์ ๋ผ๊ณ ์๊ฐ์ด ๋ค์ ๋ฐ์ ์๊ธฐ ๋๋ฌธ์ด๋ค. ๊ทธ๋ฌ๋ ์ถ์ฒ๋ฅผ ๋น๊ตํ๋ ๋ก์ง์ ์๋ฒ์ ๊ตฌํ๋ ์คํ์ด ์๋ ๋ธ๋ผ์ฐ์ ์ ๊ตฌํ๋ ์คํ์ด๋ค.
์ฌ์ค ์๋ฒ๋ ๋ฆฌ์์ค ์์ฒญ์ ์ํ ์๋ต์ ๋ง๋ํ ํด์ฃผ์๋ค. ์๋ชป์ด ์๋ ๊ฒ์ด๋ค. ํ์ง๋ง ๋ธ๋ผ์ฐ์ ๊ฐ ์ด ์๋ต์ ๋ถ์ํด์ ๋์ผ ์ถ์ฒ๊ฐ ์๋๋ฉด, ์๋ป๊ฑด ์๋ฌ๋ฅผ ๋ด๋ฟ๋ ๊ฒ์ด๋ค. (์ฌ์ค ์๋ฒ๊ฐ ํค๋ ์ ๋ณด๋ฅผ ๋ ์ค์ ๊ทธ๋ฐ๊ฒ์ด๋ค. ์ด๋ ๋ค์์ ๋ค๋ฃฌ๋ค)
๊ทธ๋์ ๋ธ๋ผ์ฐ์ ์๋ ์๋ฌ๊ฐ ๋จ์ง๋ง, ์ ์ ์๋ฒ ์ชฝ์๋ ์ ์์ ์ผ๋ก ์๋ต์ ํ๋ค๊ณ ํ๊ธฐ ๋๋ฌธ์ ๋ํญ์ ๊ฒช๋ ๊ฒ์ด๋ค. ์ฆ, ์๋ต ๋ฐ์ดํฐ๋ ๋ฉ์ฉกํ์ง๋ง ๋ธ๋ผ์ฐ์ ๋จ์์ ๋ฐ์์ ์๋๋ก ์ฐจ๋จ์ ํ ๊ฒ์ด๋ค.
๊ทธ๋์ CORS ์๋ฌ๋ฅผ ํด๊ฒฐํ๋ ๋ฐฉ์ ์ค ํ๋๋ก ํฌ๋กฌ ๋ธ๋ผ์ฐ์ ์ค์ ์ SOP ์ ์ฑ ์ ๋นํ์ฑํ ํ๋ ๋ฐฉ๋ฒ์ด ์๊ธด ํ๋ฐ ๊ถ์ฅํ์ง๋ ์๋๋ค.
๋ธ๋ผ์ฐ์ ๊ฐ ์ ์ฑ ์ผ๋ก ์ฐจ๋จ์ ํ๋ค๋ ๋ง์, ๋ธ๋ผ์ฐ์ ๋ฅผ ํตํ์ง ์๊ณ ์๋ฒ ๊ฐ์ ํต์ ์ ํ ๋๋ ์ ์ฑ ์ด ์ ์ฉ๋์ง ์๋๋ค๋ ๋ง๊ณผ ๊ฐ๋ค.
์ฆ, ํด๋ผ์ด์ธํธ ๋จ ์ฝ๋์์ API ์์ฒญ์ ํ๋๊ฒ ์๋๋ผ, ์๋ฒ ๋จ ์ฝ๋์์ ๋ค๋ฅธ ์ถ์ฒ์ ์๋ฒ๋ก API ์์ฒญ์ ํ๋ฉด CORS ์๋ฌ๋ก๋ถํฐ ์์ ๋ก์ ์ง๋ค. ๊ทธ๋์ ์ด๋ฅผ ์ด์ฉํ ํ๋ก์(Proxy) ์๋ฒ๋ผ๋ ๊ฒ์ด ์๋ค. (ํ์ )
๐ค ๊ทธ๋ผ ์ฃ๋ค ์ฐจ๋จํ๋ฉด ์ธํฐ๋ท์ด ๋๋๊ฐ?
ํ์ง๋ง ์ธํฐ๋ท์ ์ฌ๋ฌ ์ฌ๋๋ค์๊ฒ ์คํ๋ ํ๊ฒฝ์ด๊ณ , ์ด๋ฐ ํ๊ฒฝ์์ ์นํ์ด์ง์์ ๋ค๋ฅธ ์ถ์ฒ์ ์๋ ๋ฆฌ์์ค๋ฅผ ๊ฐ์ ธ์ ์ฌ์ฉํ๋ ์ผ์ ๋งค์ฐ ํํ ์ผ์ด๋ผ ๋ฌดํฑ๋๊ณ ๋ง์ ์๋ ์๋ ์ผ์ด๋ค.
๊ทธ๋์ ๋ช ๊ฐ์ง ์์ธ ์กฐํญ์ ๋๊ณ ๋ค๋ฅธ ์ถ์ฒ์ ๋ฆฌ์์ค ์์ฒญ์ด๋ผ๋ ์ด ์กฐํญ์ ํด๋นํ ๊ฒฝ์ฐ์๋ ํ์ฉํ๊ธฐ๋ก ํ๋๋ฐ, ๊ทธ ์ค ํ๋๊ฐ ๋ฐ๋ก CORS ์ ์ฑ ์ ์งํจ ๋ฆฌ์์ค ์์ฒญ์ด๋ค.
๊ต์ฐจ ์ถ์ฒ ๋ฆฌ์์ค ๊ณต์ (Cross-Origin Resource Sharing)
์ด์ฒ๋ผ ๊ต์ฐจ ์ถ์ฒ ๋ฆฌ์์ค ๊ณต์ (Cross-Origin Resource Sharing, CORS)๋ ๋จ์ด ๊ทธ๋๋ก ๋ค๋ฅธ ์ถ์ฒ์ ๋ฆฌ์์ค ๊ณต์ ์ ๋ํ ํ์ฉ/๋นํ์ฉ ์ ์ฑ ์ด๋ค.
์๋ฌด๋ฆฌ ๋ณด์์ด ์ค์ํ์ง๋ง, ๊ฐ๋ฐ์ ํ๋ค ๋ณด๋ฉด ๊ธฐ๋ฅ์ ์ด์ฉ ์ ์์ด ๋ค๋ฅธ ์ถ์ฒ ๊ฐ์ ์ํธ์์ฉ์ ํด์ผ ํ๋ ์ผ์ด์ค๋ ์์ผ๋ฉฐ, ๋ํ ์ค๋ฌด์ ์ผ๋ก ๋ค๋ฅธ ํ์ฌ์ ์๋ฒ API๋ฅผ ์ด์ฉํด์ผ ํ๋ ์ํฉ๋ ์กด์ฌํ๋ค. ๋ฐ๋ผ์ ์ด์ ๊ฐ์ ์์ธ ์ฌํญ์ ๋๊ธฐ ์ํด CORS ์ ์ฑ ์ ํ์ฉํ๋ ๋ฆฌ์์ค์ ํํด ๋ค๋ฅธ ์ถ์ฒ๋ผ๋ ๋ฐ์๋ค์ธ๋ค๋ ๊ฒ์ด๋ค.
๐ฌ ์ฐ๋ฆฌ๊ฐ ์ํ๋ CORS๋ ์ฌ์ค ํด๊ฒฐ์ฑ ์ด์๋ค
๊ฒฐ๊ตญ ์น๊ฐ๋ฐ์๋ฅผ ๊ดด๋กญํ๋ ์๋ป๊ฑด ์๋ฌ ๋ฉ์ธ์ง๋ ์ฌ์ค ๋ธ๋ผ์ฐ์ ์ SOP ์ ์ฑ ์ ๋ฐ๋ผ ๋ค๋ฅธ ์ถ์ฒ์ ๋ฆฌ์์ค๋ฅผ ์ฐจ๋จํ๋ฉด์ ๋ฐ์๋ ์๋ฌ์ด๋ฉฐ, CORS๋ ๋ค๋ฅธ ์ถ์ฒ์ ๋ฆฌ์์ค๋ฅผ ์ป๊ธฐ์ํ ํด๊ฒฐ ๋ฐฉ์ ์ด์๋ ๊ฒ์ด๋ค. ์์ฝํ์๋ฉด SOP ์ ์ฑ ์ ์๋ฐํด๋ CORS ์ ์ฑ ์ ๋ฐ๋ฅด๋ฉด ๋ค๋ฅธ ์ถ์ฒ์ ๋ฆฌ์์ค๋ผ๋ ํ์ฉํ๋ค๋ ๋ป์ด๋ค.
๊ทธ๋ผ ์ด๋ป๊ฒ CORS ์ ์ฑ ์ ๋ฐ๋ฅด๊ฒ ํ์ฌ SOP ์ ์ฑ ์ ํํผํ ์ ์์๊น? ์ด๋ฅผ ์๊ธฐ ์ํด์ ๋ธ๋ผ์ฐ์ ์ CORS ๋์ ๊ณผ์ ์ ์ดํด ๋ณด์์ผ ํ๋ค.
๐ ๋ธ๋ผ์ฐ์ ์ CORS ๊ธฐ๋ณธ ๋์ ์ดํด๋ณด๊ธฐ
1. ํด๋ผ์ด์ธํธ์์ HTTP์์ฒญ์ ํค๋์ Origin์ ๋ด์ ์ ๋ฌ
- ๊ธฐ๋ณธ์ ์ผ๋ก ์น์ HTTP ํ๋กํ ์ฝ์ ์ด์ฉํ์ฌ ์๋ฒ์ ์์ฒญ์ ๋ณด๋ด๊ฒ ๋๋๋ฐ,
- ์ด๋ ๋ธ๋ผ์ฐ์ ๋ ์์ฒญ ํค๋์ Origin ์ด๋ผ๋ ํ๋์ ์ถ์ฒ๋ฅผ ํจ๊ป ๋ด์ ๋ณด๋ด๊ฒ ๋๋ค.
2. ์๋ฒ๋ ์๋ตํค๋์ Access-Control-Allow-Origin์ ๋ด์ ํด๋ผ์ด์ธํธ๋ก ์ ๋ฌํ๋ค.
- ์ดํ ์๋ฒ๊ฐ ์ด ์์ฒญ์ ๋ํ ์๋ต์ ํ ๋ ์๋ต ํค๋์
Access-Control-Allow-Origin์ด๋ผ๋ ํ๋๋ฅผ ์ถ๊ฐํ๊ณ ๊ฐ์ผ๋ก '์ด ๋ฆฌ์์ค๋ฅผ ์ ๊ทผํ๋ ๊ฒ์ด ํ์ฉ๋ ์ถ์ฒ url'์ ๋ด๋ ค๋ณด๋ธ๋ค.
3. ํด๋ผ์ด์ธํธ์์ Origin๊ณผ ์๋ฒ๊ฐ ๋ณด๋ด์ค Access-Control-Allow-Origin์ ๋น๊ตํ๋ค.
- ์ดํ ์๋ต์ ๋ฐ์ ๋ธ๋ผ์ฐ์ ๋ ์์ ์ด ๋ณด๋๋ ์์ฒญ์ Origin๊ณผ ์๋ฒ๊ฐ ๋ณด๋ด์ค ์๋ต์ Access-Control-Allow-Origin์ ๋น๊ตํด๋ณธ ํ ์ฐจ๋จํ ์ง ๋ง์ง๋ฅผ ๊ฒฐ์ ํ๋ค.
- ๋ง์ฝ ์ ํจํ์ง ์๋ค๋ฉด ๊ทธ ์๋ต์ ์ฌ์ฉํ์ง ์๊ณ ๋ฒ๋ฆฐ๋ค. (CORS ์๋ฌ !!)
- ์์ ๊ฒฝ์ฐ์๋ ๋๋ค http://localhost:3000์ด๊ธฐ ๋๋ฌธ์ ์ ํจํ๋ ๋ค๋ฅธ ์ถ์ฒ์ ๋ฆฌ์์ค๋ฅผ ๋ฌธ์ ์์ด ๊ฐ์ ธ์ค๊ฒ ๋๋ค.
๐ฌ ๊ฒฐ๊ตญ CORS ํด๊ฒฐ์ฑ ์ ์๋ฒ์ ํ์ฉ์ด ํ์
์์ ๋ธ๋ผ์ฐ์ ์ CORS ๋์ ๊ณผ์ ์ ์ดํด๋ณด๋, ๊ธธ๊ณ ๊ธธ์๋ ์ฌ์ ์ ๊ฒฐ๋ก ์ ์๋ฒ์์ Access-Control-Allow-Origin ํค๋์ ํ์ฉํ ์ถ์ฒ๋ฅผ ๊ธฐ์ฌํด์ ํด๋ผ์ด์ธํธ์ ์๋ตํ๋ฉด ๋๋ ๊ฒ์ด์๋ค. ์ฆ, ๋ฐฑ์๋ ๊ฐ๋ฐ์๊ฐ ๊ณ ์ณ์ผ๋ ๋ถ๋ถ์ธ ๊ฒ์ด๋ค.
๊ทธ๋ ๋ค๋ฉด ํด๋ผ์ด์ธํธ์์ ๋ฏธ๋ฆฌ ์๋ฐ์คํฌ๋ฆฝํธ๋ก origin ํค๋๊ฐ์ ์์กฐํ๋ฉด ๋์ง ์์๊น ์ถ์ง๋ง, ๋ธ๋ผ์ฐ์ ์์ ์ด๋ฅผ ๊ฐ์งํ์ฌ ์ฐจ๋จํ๊ธฐ ๋๋ฌธ์ ๊ฒฐ๋ก ์ ๋ถ๊ฐ๋ฅํ๋ค.
CORS ์๋ ๋ฐฉ์ 3๊ฐ์ง ์๋๋ฆฌ์ค
๋ฐ๋ก ์์์ ์ดํด๋ณธ CORS ๋์ ํ๋ฆ์ ์ดํดํ๊ธฐ ์ฝ๊ฒ ํ๊ธฐ ์ํด ๊ธฐ๋ณธ์ ์ธ ์๋ ํ๋ฆ์ ๋ณด์ฌ์ค ๊ฒ์ด๊ณ , ์ค์ ๋ก๋ CORS๊ฐ ๋์ํ๋ ๋ฐฉ์์ ํ ๊ฐ์ง๊ฐ ์๋๋ผ ์ธ ๊ฐ์ง์ ์๋๋ฆฌ์ค์ ๋ฐ๋ผ ๋ณ๊ฒฝ๋๊ธฐ ๋๋ฌธ์, CORS๋ฅผ ์ ๋ณตํ๊ธฐ ์ํด์ ์ด๋ค์ ๋ชจ๋ ์ ํ์๊ฐ ์๋ค. (๊ณต๋ถ๊ฐ ๋์ด์๋ค โน๏ธ)
๋ค๋ง ์ด ๋ถ๋ถ์ ๋น์ฅ CORS๋ฅผ ํด๊ฒฐํ๋๋ฐ ์์ด ํ์ ์ง์์ ์๋์ง๋ง, ๋ง์ผ ๋ ์๋ถ์ด ๋จ์ ์์ฒญ์ ๋ ๋ ์ฟ ํค๋ ํ ํฐ๊ณผ ๊ฐ์ ์ธ์ฆ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฅธ ์ถ์ฒ์ ์๋ฒ์ ์์ฒญ์ ํด์ผํ๋ค๋ฉด ์ด ์น์ ์ ์ง์์ ํ์์ด๋ค. ๋ํ ์ฐ๋ฆฌ๊ฐ ์ธํฐ๋ท์ ๋ฐฐ์ธ๋ TCP / UDP์ ๋ด๋ถ ํต์ ๊ณผ์ ์ ๋ฐฐ์ ๋ฏ์ด, ๋ธ๋ผ์ฐ์ ์ ์ธ๋ถ์ ์ธ CORS ํต์ ๋์ ๊ณผ์ ์ ์ดํด๋ด์ผ ๋์ค์ ์ต์ ํ ์์ ์ ํ ์ ์๊ธฐ ๋๋ฌธ์ ํ์ต์ด ๊ถ์ฅ๋๋ ๋ฐ์ด๋ค.
์๋น ์์ฒญ (Preflight Request)
์ฌ์ค ๋ธ๋ผ์ฐ์ ๋ ์์ฒญ์ ๋ณด๋ผ๋ ํ๋ฒ์ ๋ฐ๋ก ๋ณด๋ด์ง์๊ณ , ๋จผ์ ์๋น ์์ฒญ์ ๋ณด๋ด ์๋ฒ์ ์ ํต์ ๋๋์ง ํ์ธํ ํ ๋ณธ ์์ฒญ์ ๋ณด๋ธ๋ค.
์ฆ, ์๋น ์์ฒญ์ ์ญํ ์ ๋ณธ ์์ฒญ์ ๋ณด๋ด๊ธฐ ์ ์ ๋ธ๋ผ์ฐ์ ์ค์ค๋ก ์์ ํ ์์ฒญ์ธ์ง ๋ฏธ๋ฆฌ ํ์ธํ๋ ๊ฒ์ด๋ค.
์ด๋ ๋ธ๋ผ์ฐ์ ๊ฐ ์๋น์์ฒญ์ ๋ณด๋ด๋ ๊ฒ์ Preflight๋ผ๊ณ ๋ถ๋ฅด๋ฉฐ, ์ด ์๋น์์ฒญ์ HTTP ๋ฉ์๋๋ฅผ GET์ด๋ POST๊ฐ ์๋ OPTIONS๋ผ๋ ์์ฒญ์ด ์ฌ์ฉ๋๋ค๋ ๊ฒ์ด ํน์ง์ด๋ค.
์๋ฅผ๋ค์ด ์๋ฐ์คํฌ๋ฆฝํธ๋ก ๋ค์ api ์์ฒญ์ ๋ณด๋ธ๋ค๊ณ ๊ฐ์ ํด๋ณด์.
- ์๋ฐ์คํฌ๋ฆฝํธ์
fetch()๋ฉ์๋๋ฅผ ํตํด ๋ฆฌ์์ค๋ฅผ ๋ฐ์์ค๋ ค๊ณ ํ๋ค. - ๋ธ๋ผ์ฐ์ ๋ ์๋ฒ๋ก HTTP OPTIONS ๋ฉ์๋๋ก ์๋น ์์ฒญ(Preflight)์ ๋จผ์ ๋ณด๋ธ๋ค.
- Origin ํค๋์ ์์ ์ ์ถ์ฒ๋ฅผ ๋ฃ๋๋ค.
- Access-Control-Request-Method ํค๋์ ์ค์ ์์ฒญ์ ์ฌ์ฉํ ๋ฉ์๋๋ฅผ ์ค์ ํ๋ค.
- Access-Control-Request-Headers ํค๋์ ์ค์ ์์ฒญ์ ์ฌ์ฉํ ํค๋๋ค์ ์ค์ ํ๋ค.
- ์๋ฒ๋ ์ด ์๋น ์์ฒญ์ ๋ํ ์๋ต์ผ๋ก ์ด๋ค ๊ฒ์ ํ์ฉํ๊ณ ์ด๋ค๊ฒ์ ๊ธ์งํ๊ณ ์๋์ง์ ๋ํ ํค๋ ์ ๋ณด๋ฅผ ๋ด์์ ๋ธ๋ผ์ฐ์ ๋ก ๋ณด๋ด์ค๋ค.
- Access-Control-Allow-Origin ํค๋์ ํ์ฉ๋๋ Origin๋ค์ ๋ชฉ๋ก์ ์ค์ ํ๋ค.
- Access-Control-Allow-Methods ํค๋์ ํ์ฉ๋๋ ๋ฉ์๋๋ค์ ๋ชฉ๋ก์ ์ค์ ํ๋ค.
- Access-Control-Allow-Headers ํค๋์ ํ์ฉ๋๋ ํค๋๋ค์ ๋ชฉ๋ก์ ์ค์ ํ๋ค.
- Access-Control-Max-Age ํค๋์ ํด๋น ์๋น ์์ฒญ์ด ๋ธ๋ผ์ฐ์ ์ ์บ์ ๋ ์ ์๋ ์๊ฐ์ ์ด ๋จ์๋ก ์ค์ ํ๋ค.
- ์ดํ ๋ธ๋ผ์ฐ์ ๋ ๋ณด๋ธ ์์ฒญ๊ณผ ์๋ฒ๊ฐ ์๋ตํด์ค ์ ์ฑ ์ ๋น๊ตํ์ฌ, ํด๋น ์์ฒญ์ด ์์ ํ์ง ํ์ธํ๊ณ ๋ณธ ์์ฒญ์ ๋ณด๋ด๊ฒ ๋๋ค.
- ์๋ฒ๊ฐ ๋ณธ ์์ฒญ์ ๋ํ ์๋ต์ ํ๋ฉด ์ต์ข ์ ์ผ๋ก ์ด ์๋ต ๋ฐ์ดํฐ๋ฅผ ์๋ฐ์ค๋ฆฝํธ๋ก ๋๊ฒจ์ค๋ค.
โ๏ธ ๊ฐ๋ฐ์ ๋๊ตฌ์์ ์๋น ์์ฒญ ํ์ธํ๊ธฐ
์์ ํ๋ก์ฐ๋ ๋ธ๋ผ์ฐ์ ์ ๊ฐ๋ฐ์ ๋๊ตฌ์ ๋คํธ์ํฌ ํญ์ ํตํด ๊ฐ๋จํ ์ฌํ์ด ๊ฐ๋ฅํ๋ค.
์ค์ ๋ก ์๋ฐ์คํฌ๋ฆฝํธ ์ฝ๋๋ก api ์์ฒญ์ ๋ณด๋ด๋ฉด, ํฌ๋กฌ ๊ฐ๋ฐ์ ๋๊ตฌ์์ ํด๋ผ์ด์ธํธ์ ์๋ฒ๊ฐ ๋ณธ ์์ฒญ(xhr)์ ๋ณด๋ด๊ธฐ ์ ์ ์๋น ์์ฒญ(preflight) ํต์ ์ ํ๊ณ ์๋ ๊ฒ์ ๋ณผ์ ์๋ค.
await fetch("http://localhost:4000/users/location-registration", {"method":"DELETE"})
์์ ์ฌ์ง์์๋ ์์ฒญ ํค๋์ Origin๊ณผ ์๋ต ํค๋์ Access-Control-Allow-Origin ์ URL๊ฐ์ด ์๋ก ๊ฐ์ ๋ค๋ฅธ ์ถ์ฒ๋ผ๋ CORS(๋ค๋ฅธ ์ถ์ฒ ๋ฆฌ์์ค ๊ณต์ )๊ฐ ํ์ฉ๋์ ์ ์ ์๋ต์ ๋ฐ๊ฒ ๋๋ค.
๋ง์ผ ์ด ๋์ด ๋ค๋ฅด๊ฒ๋๋ฉด ๋ธ๋ผ์ฐ์ ๋ ์ด ์์ฒญ์ด CORS ์ ์ฑ ์ ์๋ฐํ๋ค๊ณ ํ๋จํ๊ณ ์ ๋ช ๋์ ์๋ฌ๋ฅผ ๋ด๋ฑ๊ฒ ๋๋ ๊ฒ์ด๋ค.
โฐ ์๋น ์์ฒญ์ ๋ฌธ์ ์ ๊ณผ ์บ์ฑ
์์ฒญ์ ๋ณด๋ด๊ธฐ ์ ์ OPTIONS ๋ฉ์๋๋ก ์๋น ์์ฒญ์ ๋ณด๋ด ๋ณด์์ ๊ฐํํ๋ ๋ชฉ์ ์ ์ทจ์ง๋ ์ข๋ค. ๊ทธ๋ฌ๋ ๊ฒฐ๊ตญ์ ์ค์ ์์ฒญ์ ๊ฑธ๋ฆฌ๋ ์๊ฐ์ด ๋์ด๋๊ฒ ๋์ด ์ดํ๋ฆฌ์ผ์ด์ ์ฑ๋ฅ์ ์ํฅ์ ๋ฏธ์น๋ ํฌ๋ํฐ ๋จ์ ์ด ์๋ค.
ํนํ ์ํํ๋ API ํธ์ถ ์๊ฐ ๋ง์ผ๋ฉด ๋ง์ ์๋ก ์๋น ์์ฒญ์ผ๋ก ์ธํด ์๋ฒ ์์ฒญ์ ๋ฐฐ๋ก ๋ณด๋ด๊ฒ ๋๋ ๋น์ฉ ์ ์ธ ์ธก๋ฉด์์ ํ๊ฐ ๋ ์ ์๋ค. ๋ฐ๋ผ์ ๋ธ๋ผ์ฐ์ ์บ์(Cache) ๋ฅผ ์ด์ฉํด Access-Control-Max-Age ํค๋์ ์บ์๋ ์๊ฐ์ ๋ช
์ํด ์ฃผ๋ฉด, ์ด Preflight ์์ฒญ์ ์บ์ฑ ์์ผ ์ต์ ํ๋ฅผ ์์ผ์ค ์ ์๋ค.
์๋น ์์ฒญ ์บ์ฑ ๊ธฐ๊ฐ์ ๋ํด์๋, ํ์ด์ดํญ์ค ๋ธ๋ผ์ฐ์ ๋ 86400์ด(24์๊ฐ) ๊น์ง ๊ฐ๋ฅํ์ง๋ง ํฌ๋ก๋ฏธ์ ๊ธฐ๋ฐ ๋ธ๋ผ์ฐ์ ๋ 7200์ด(2์๊ฐ)์ด ์ต๋์ด๋ค.
์๋น ์์ฒญ ์บ์๋ ๋ค๋ฅธ ์บ์ฑ ๋งค์ปค๋์ฆ๊ณผ ์ ์ฌํ๊ฒ ์๋ํ๋ค.
- ๋ธ๋ผ์ฐ์ ๋ ์๋น(Preflight) ์์ฒญ์ ํ ๋๋ง๋ค, ๋จผ์ Preflight ์บ์๋ฅผ ํ์ธํ์ฌ ํด๋น ์์ฒญ์ ๋ํ ์๋ต์ด ์๋์ง ํ์ธํ๋ค.
- ๋ง์ผ ์๋ต์ด ์บ์ฑ ๋์ด ์์ง ์๋ค๋ฉด, ์๋ฒ์ ์๋น ์์ฒญ์ ๋ณด๋ด ์ธ์ฆ ์ ์ฐจ๋ฅผ ๋ฐ๋๋ค.
- ๋ง์ผ ์๋ฒ๋ก ๋ถํฐ Access-Control-Max-Age ์๋ต ํค๋๋ฅผ ๋ฐ๋๋ค๋ฉด ๊ทธ ๊ธฐ๊ฐ ๋์ ๋ธ๋ผ์ฐ์ ์บ์์ ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํ๋ค.
- ๋ค์ ์์ฒญ์ ๋ณด๋ด๊ณ ๋ง์ผ ์๋ต์ด ์บ์ฑ ๋์ด ์๋ค๋ฉด, ์๋น ์์ฒญ์ ์๋ฒ๋ก ๋ณด๋ด์ง ์๊ณ ๋์ ์บ์๋ ์๋ต์ ์ฌ์ฉํ๋ค.
๋จ์ ์์ฒญ (Simple Request)
๋จ์ ์์ฒญ์ ๋ง๊ทธ๋๋ก ์๋น ์์ฒญ(Prefilght)์ ์๋ตํ๊ณ ๋ฐ๋ก ์๋ฒ์ ์งํ์ผ๋ก ๋ณธ ์์ฒญ์ ๋ณด๋ธ ํ, ์๋ฒ๊ฐ ์ด์ ๋ํ ์๋ต์ ํค๋์ Access-Control-Allow-Origin ํค๋๋ฅผ ๋ณด๋ด์ฃผ๋ฉด ๋ธ๋ผ์ฐ์ ๊ฐ CORS์ ์ฑ ์๋ฐ ์ฌ๋ถ๋ฅผ ๊ฒ์ฌํ๋ ๋ฐฉ์์ด๋ค.
๋ค๋ง, ์ฌํํ ๋งํผ ํน์ ์กฐ๊ฑด์ ๋ง์กฑํ๋ ๊ฒฝ์ฐ์๋ง ์๋น ์์ฒญ์ ์๋ตํ ์ ์๋ค.
๋ํ์ ์ผ๋ก ์๋ 3๊ฐ์ง ๊ฒฝ์ฐ๋ฅผ ๋ง์กฑ ํ ๋ ๋ง ๊ฐ๋ฅํ๋ค.
- ์์ฒญ์ ๋ฉ์๋๋ 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์ค ํ๋์ฌ์ผํ๋ค. ์๋ ๊ฒฝ์ฐ ์๋น ์์ฒญ์ผ๋ก ๋์๋๋ค.
์ด์ฒ๋ผ ๋ค์ ๊น๋ค๋ก์ด ์กฐ๊ฑด๋ค์ด ๋ง๊ธฐ ๋๋ฌธ์, ์ ์กฐ๊ฑด์ ๋ชจ๋ ๋ง์กฑ๋์ด ๋จ์ ์์ฒญ์ด ์ผ์ด๋๋ ์ํฉ์ ๋๋ฌผ๋ค๊ณ ๋ณด๋ฉด ๋๋ค.
์๋ํ๋ฉด ๋๋ถ๋ถ HTTP API ์์ฒญ์ text/xml ์ด๋ application/json ์ผ๋ก ํต์ ํ๊ธฐ ๋๋ฌธ์ 3๋ฒ์งธ Content-Type์ด ์๋ฐ๋๊ธฐ ๋๋ฌธ์ด๋ค.
๋ฐ๋ผ์ ๋๋ถ๋ถ์ API ์์ฒญ์ ๊ทธ๋ฅ ์๋น ์์ฒญ(preflight)์ผ๋ก ์ด๋ฃจ์ด์ง๋ค ๋ผ๊ณ ์ดํดํ๋ฉด ๋๋ค.
์ธ์ฆ๋ ์์ฒญ (Credentialed Request)
์ธ์ฆ๋ ์์ฒญ์ ํด๋ผ์ด์ธํธ์์ ์๋ฒ์๊ฒ ์๊ฒฉ ์ธ์ฆ ์ ๋ณด(Credential)๋ฅผ ์ค์ด ์์ฒญํ ๋ ์ฌ์ฉ๋๋ ์์ฒญ์ด๋ค.
์ฌ๊ธฐ์ ๋งํ๋ ์๊ฒฉ ์ธ์ฆ ์ ๋ณด๋ ์ธ์ ID๊ฐ ์ ์ฅ๋์ด์๋ ์ฟ ํค(Cookie) ํน์ Authorization ํค๋์ ์ค์ ํ๋ ํ ํฐ ๊ฐ ๋ฑ์ ์ผ์ปซ๋๋ค.
์ฆ, ํด๋ผ์ด์ธํธ์์ ์ผ๋ฐ์ ์ธ JSON ๋ฐ์ดํฐ ์ธ์๋ ์ฟ ํค ๊ฐ์ ์ธ์ฆ ์ ๋ณด๋ฅผ ํฌํจํด์ ๋ค๋ฅธ ์ถ์ฒ์ ์๋ฒ๋ก ์ ๋ฌํ ๋ CORS์ ์ธ๊ฐ์ง ์์ฒญ์ค ํ๋์ธ ์ธ์ฆ๋ ์์ฒญ์ผ๋ก ๋์๋๋ค๋ ๋ง์ด๋ฉฐ, ์ด๋ ๊ธฐ์กด์ ๋จ์ ์์ฒญ์ด๋ ์๋น ์์ฒญ๊ณผ๋ ์ด์ง ๋ค๋ฅธ ์ธ์ฆ ํํ๋ก ํต์ ํ๊ฒ ๋๋ค.
1. ํด๋ผ์ด์ธํธ์์ ์ธ์ฆ ์ ๋ณด๋ฅผ ๋ณด๋ด๋๋ก ์ค์ ํ๊ธฐ
๊ธฐ๋ณธ์ ์ผ๋ก ๋ธ๋ผ์ฐ์ ๊ฐ ์ ๊ณตํ๋ ์์ฒญ API ๋ค์ ๋ณ๋์ ์ต์ ์์ด ๋ธ๋ผ์ฐ์ ์ ์ฟ ํค์ ๊ฐ์ ์ธ์ฆ๊ณผ ๊ด๋ จ๋ ๋ฐ์ดํฐ๋ฅผ ํจ๋ถ๋ก ์์ฒญ ๋ฐ์ดํฐ์ ๋ด์ง ์๋๋ก ๋์ด์๋ค.
์ด๋ ์์ฒญ์ ์ธ์ฆ๊ณผ ๊ด๋ จ๋ ์ ๋ณด๋ฅผ ๋ด์ ์ ์๊ฒ ํด์ฃผ๋ ์ต์
์ด ๋ฐ๋ก credentials ์ต์
์ด๋ค. ์ด ์ต์
์๋ 3๊ฐ์ง์ ๊ฐ์ ์ฌ์ฉํ ์ ์์ผ๋ฉฐ, ๊ฐ ๊ฐ๋ค์ด ๊ฐ์ง๋ ์๋ฏธ๋ ์๋์ ๊ฐ๋ค.
์ต์ ๊ฐ | ์ค๋ช |
same-origin(๊ธฐ๋ณธ๊ฐ) | ๊ฐ์ ์ถ์ฒ ๊ฐ ์์ฒญ์๋ง ์ธ์ฆ ์ ๋ณด๋ฅผ ๋ด์ ์ ์๋ค. |
include | ๋ชจ๋ ์์ฒญ์ ์ธ์ฆ ์ ๋ณด๋ฅผ ๋ด์ ์ ์๋ค. |
omit | ๋ชจ๋ ์์ฒญ์ ์ธ์ฆ ์ ๋ณด๋ฅผ ๋ด์ง ์๋๋ค. |
๋ง์ผ ์ด๋ฌํ ๋ณ๋์ ์ค์ ์ ํด์ฃผ์ง ์์ผ๋ฉด ์ฟ ํค ๋ฑ์ ์ธ์ฆ ์ ๋ณด๋ ์ ๋๋ก ์๋์ผ๋ก ์๋ฒ์๊ฒ ์ ์ก๋์ง ์๋๋ค.
์๋ฒ์ ์ธ์ฆ๋ ์์ฒญ์ ๋ณด๋ด๋ ๋ฐฉ๋ฒ์ผ๋ก๋ fetch ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ axios, jQuery ๋ผ์ด๋ธ๋ฆฌ๋ฆฌ ๋ฑ ๋ค์ํ๋ค. ์ด๋ค ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋๋์ ๋ฐ๋ผ ์ฝ๊ฐ credentials ์ต์
์ ์ง์ ํ๋ ๋ฌธ๋ฒ์ด ๋ค๋ฅด๋ ์ด๋ค์ ๋ชจ๋ ์๊ฐํด ๋ณธ๋ค.
// fetch ๋ฉ์๋
fetch("https://example.com:1234/users/login", {
method: "POST",
credentials: "include", // ํด๋ผ์ด์ธํธ์ ์๋ฒ๊ฐ ํต์ ํ ๋ ์ฟ ํค์ ๊ฐ์ ์ธ์ฆ ์ ๋ณด ๊ฐ์ ๊ณต์ ํ๊ฒ ๋ค๋ ์ค์
body: JSON.stringify({
userId: 1,
}),
})
// axios ๋ผ์ด๋ธ๋ฌ๋ฆฌ
axios.post('https://example.com:1234/users/login', {
profile: { username: username, password: password }
}, {
withCredentials: true // ํด๋ผ์ด์ธํธ์ ์๋ฒ๊ฐ ํต์ ํ ๋ ์ฟ ํค์ ๊ฐ์ ์ธ์ฆ ์ ๋ณด ๊ฐ์ ๊ณต์ ํ๊ฒ ๋ค๋ ์ค์
})
// jQuery ๋ผ์ด๋ธ๋ฌ๋ฆฌ
$.ajax({
url: "https://example.com:1234/users/login",
type: "POST",
contentType: "application/json; charset=utf-8",
dataType: "json",
xhrFields: {
withCredentials: true // ํด๋ผ์ด์ธํธ์ ์๋ฒ๊ฐ ํต์ ํ ๋ ์ฟ ํค์ ๊ฐ์ ์ธ์ฆ ์ ๋ณด ๊ฐ์ ๊ณต์ ํ๊ฒ ๋ค๋ ์ค์
},
success: function (retval, textStatus) {
console.log( JSON.stringify(retval));
}
});
2. ์๋ฒ์์ ์ธ์ฆ๋ ์์ฒญ์ ๋ํ ํค๋ ์ค์ ํ๊ธฐ
์๋ฒ๋ ๋ง์ฐฌ๊ฐ์ง๋ก ์ด๋ฌํ ์ธ์ฆ๋ ์์ฒญ์ ๋ํด ์ผ๋ฐ์ ์ธ CORS ์์ฒญ๊ณผ๋ ๋ค๋ฅด๊ฒ ๋์ํด์ค์ผ ํ๋ค.
- ์๋ต ํค๋์
Access-Control-Allow-Credentialsํญ๋ชฉ์ true๋ก ์ค์ ํด์ผ ํ๋ค. - ์๋ต ํค๋์
Access-Control-Allow-Origin์ ๊ฐ์ ์์ผ๋์นด๋ ๋ฌธ์("*")๋ ์ฌ์ฉํ ์ ์๋ค. - ์๋ต ํค๋์
Access-Control-Allow-Methods์ ๊ฐ์ ์์ผ๋์นด๋ ๋ฌธ์("*")๋ ์ฌ์ฉํ ์ ์๋ค. - ์๋ต ํค๋์
Access-Control-Allow-Headers์ ๊ฐ์ ์์ผ๋์นด๋ ๋ฌธ์("*")๋ ์ฌ์ฉํ ์ ์๋ค.
์ฆ, ์๋ต์ Access-Control-Allow-Origin ํค๋๊ฐ ์์ผ๋์นด๋(*)๊ฐ ์๋ ๋ถ๋ช ํ Origin์ผ๋ก ์ค์ ๋์ด์ผ ํ๊ณ , Access-Control-Allow-Credentials ํค๋๋ true๋ก ์ค์ ๋์ด์ผ ํ๋ค๋ ๋ป์ด๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด ๋ธ๋ผ์ฐ์ ์ CORS ์ ์ฑ ์ ์ํด ์๋ต์ด ๊ฑฐ๋ถ๋๋ค. (์ธ์ฆ ์ ๋ณด๋ ๋ฏผ๊ฐํ ์ ๋ณด์ด๊ธฐ ๋๋ฌธ์ ์ถ์ฒ๋ฅผ ์ ํํ๊ฒ ์ค์ ํด์ฃผ์ด์ผ ํ๋ค)
๋ง์ผ ์ด๋ฅผ ์ด๊ธธ๊ฒฝ์ฐ ์๋์ ๊ฐ์ ๋๋ค๋ฅธ ์ข ๋ฅ์ CORS ์๋ฌ ๋ฉ์ธ์ง๋ฅผ ์ ํ๊ฒ ๋ ๊ฒ์ด๋ค.
์์ ๊ณผ์ ์ ๊ทธ๋ฆผ์ผ๋ก ๋ํ๋ด๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
์ฐธ๊ณ ๋ก ์ธ์ฆ๋ ์์ฒญ ์ญ์ ์ญ์ ์๋น ์์ฒญ ์ฒ๋ผ preflight๊ฐ ๋จผ์ ์ผ์ด๋๋ค. ์์ ๊ทธ๋ฆผ์์๋ ๋จ์ GET ์์ฒญ์ด๊ธฐ ๋๋ฌธ์ ์๋น ์์ฒญ์ ์๋ต ๋์๋ค.
CORS 3๊ฐ์ง ์๋๋ฆฌ์ค ์๋ ์ฒดํ ์ฌ์ดํธ
์์ 3๊ฐ์ง CORS ์๋๋ฆฌ์ค๋ฅผ ์ฝ๋๋ก ๋ฏธ๋ฆฌ ์ฒดํํ ์ ์๋ ์ข์ ์ฌ์ดํธ๊ฐ ์์ด์ ์๊ฐํด ๋ณธ๋ค.
์๋ฒ์์์ ์ค์ ํํ์ ํด๋ผ์ด์ธํธ์์์ ์์ฒญ ํํ์ ๋ฐ๋ผ CORS ์๋ฌ๊ฐ ๋๊ธฐ๋ ํ๊ณ ํต๊ณผ๋๊ธฐ ํ๋ ์์ ๋ฅผ ๋ณผ ์ ์๋ค.
CORS๋ฅผ ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ ์ด์ ๋ฆฌ ๐
1. Chrome ํ์ฅ ํ๋ก๊ทธ๋จ ์ด์ฉ
Chrome์์๋ CORS ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํ ํ์ฅ ํ๋ก๊ทธ๋จ์ ์ ๊ณตํด์ค๋ค.
์๋ ๋งํฌ์์ 'Allow CORS: Access-Control-Allow-Origin' ํฌ๋กฌ ํ์ฅ ํ๋ก๊ทธ๋จ์์ ์ค์น ํด์ค๋ค.
๊ทธ๋ฌ๋ฉด ๋ธ๋ผ์ฐ์ ์ค๋ฅธ์ชฝ ์๋จ์์ ํ์ฅ ํ๋ก๊ทธ๋จ์ ํ์ฑํ ์ํฌ ์ ์๋ค. ํด๋น ํ๋ก๊ทธ๋จ์ ํ์ฑํ ์ํค๊ฒ ๋๋ฉด, ๋ก์ปฌ(localhost) ํ๊ฒฝ์์ API๋ฅผ ํ ์คํธ ์, CORS ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ค.
์๋ฒ ํ ์คํธ ํ๊ฒฝ์์ ๊ณ ๋ฏผํ์ง ์๊ณ ๋น ๋ฅด๊ฒ CORS๋ฅผ ํด๊ฒฐํ๋๋ฐ ์ข์ ์ ํ์ง์ผ ๊ฒ์ด๋ค.
2. ํ๋ก์ ์ฌ์ดํธ ์ด์ฉํ๊ธฐ
ํ๋ก์(Proxy)๋ ํด๋ผ์ด์ธํธ์ ์๋ฒ ์ฌ์ด์ ์ค๊ณ ๋๋ฆฌ์ ์ด๋ผ๊ณ ๋ณด๋ฉด ๋๋ค.
์ฆ, ํ๋ก ํธ์์ ์ง์ ์๋ฒ์ ๋ฆฌ์์ค๋ฅผ ์์ฒญ์ ํ๋๋ ์๋ฒ์์ ๋ฐ๋ก ์ค์ ์ ์ํด์ค์ CORS ์๋ฌ๊ฐ ๋ฌ๋ค๋ฉด, ๋ชจ๋ ์ถ์ฒ๋ฅผ ํ์ฉํ ์๋ฒ ๋๋ฆฌ์ ์ ํตํด ์์ฒญ์ ํ๋ฉด ๋๋ ๊ฒ์ด๋ค.
๋ค๋ง ํ์ฌ ๋ฌด๋ฃ ํ๋ก์ ์๋ฒ ๋์ฌ ์๋น์ค๋ค์ ๋ชจ๋ ์ ์ฉ ์ฌ๋ก ๋๋ฌธ์ api ์์ฒญ ํ์ ์ ํ์ ๋์ด ์ค์ ์์๋ ์ฌ์ฉํ๊ธฐ ๋ฌด๋ฆฌ์ด๋ค. ๋ฐ๋ผ์ ํ ์คํธ์ฉ์ด๋ ๋ง๋ณด๊ธฐ์ฉ์ผ๋ก ์ฌ์ฉํ๋, ์ค์ ์์๋ ์ง์ ํ๋ก์ ์๋ฒ๋ฅผ ๊ตฌ์ถํ์ฌ ์ฌ์ฉํ์ฌ์ผ ํ๋ค.
heroku ํ๋ก์ ์๋ฒ
- http://cors-anywhere.herokuapp.com/corsdemo
- ์์ ์ฌ์ดํธ๋ก ๊ฐ์ ๋ฒํผ์ ๋๋ฅด๊ณ ๋ฐ๋ชจ ์๋ฒ๋ฅผ ํ์ฑํ ์ํจ๋ค.
- ๋ค๋ง ์๊ฐ ์ ํ์ด ์๊ธฐ ๋๋ฌธ์ ์ผ์์ ์ธ ํด๊ฒฐ ๋ฐฉํธ ์ด๋ค.
const url = 'https://google.com' // ์ด ๋ถ๋ถ์ ์ด์ฉํ๋ ์๋ฒ URL๋ก ๋ณ๊ฒฝ
fetch(`https://cors-anywhere.herokuapp.com/${url}`)
.then((response) => response.text())
.then((data) => console.log(data));
cors proxy app ํ๋ก์ ์๋ฒ
- raravel๋์ด ๋ง๋์ ์๋น์ค
- axios ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น๊ฐ ํ์ํ๋ค.
<script src='https://cdnjs.cloudflare.com/ajax/libs/axios/1.1.3/axios.min.js'></script>
<script>
axios({
url: 'https://cors-proxy.org/api/',
method: 'get',
headers: {
'cors-proxy-url' : 'https://google.com/' // ์ด ๋ถ๋ถ์ ์ด์ฉํ๋ ์๋ฒ URL๋ก ๋ณ๊ฒฝ
},
}).then((res) => {
console.log(res.data);
})
</script>
cors.sh ํ๋ก์ ์๋ฒ
- https://cors.sh/
- ์ค์ ์์ ์ด์ฉํ๋ ค๋ฉด ์ ๋ฃ ๊ฒฐ์ ๊ฐ ํ์ํ๋ฏ ํ๋ค. (free๋ ๊ฐ๋ฅ)
const url = 'https://google.com' // ์ด ๋ถ๋ถ์ ์ด์ฉํ๋ ์๋ฒ URL๋ก ๋ณ๊ฒฝ
fetch(`https://proxy.cors.sh/${url}`)
.then((response) => response.text())
.then((data) => console.log(data));
3. ์๋ฒ์์ Access-Control-Allow-Origin ํค๋ ์ธํ ํ๊ธฐ
์ง์ ์๋ฒ์์ HTTP ํค๋ ์ค์ ์ ํตํด ์ถ์ฒ๋ฅผ ํ์ฉํ๊ฒ ์ค์ ํ๋ ๊ฐ์ฅ ์ ์์ ์ธ ํด๊ฒฐ์ฑ ์ด๋ค.
์๋ฒ์ ์ข ๋ฅ๋ ๋ ธ๋ ์๋ฒ, ์คํ๋ง ์๋ฒ, ์ํ์น ์๋ฒ ๋ฑ ์ฌ๋ฌ๊ฐ์ง๊ฐ ์์ผ๋, ์ด์ ๋ํ ๊ฐ๊ฐ ํด๊ฒฐ์ฑ ์ ๋์ดํด๋ณธ๋ค.
๊ฐ ์๋ฒ์ ๋ฌธ๋ฒ์ ๋ง๊ฒ ์์ HTTP ํค๋๋ฅผ ์ถ๊ฐํด ์ฃผ๋ฉด ๋๋ค.
์ฐธ๊ณ ๋ก CORS์ ์ฐ๊ด๋ HTTP ํค๋ ๊ฐ์ผ๋ก๋ ๋ค์ ์ข ๋ฅ๊ฐ ์๋ค. (์ด๋ค์ ๋ชจ๋ ์ค์ ํ ํ์๋ ์๋ค)
# ํค๋์ ์์ฑ๋ ์ถ์ฒ๋ง ๋ธ๋ผ์ฐ์ ๊ฐ ๋ฆฌ์์ค๋ฅผ ์ ๊ทผํ ์ ์๋๋ก ํ์ฉํจ.
# * ์ด๋ฉด ๋ชจ๋ ๊ณณ์ ๊ณต๊ฐ๋์ด ์์์ ์๋ฏธํ๋ค.
Access-Control-Allow-Origin : https://naver.com
# ๋ฆฌ์์ค ์ ๊ทผ์ ํ์ฉํ๋ HTTP ๋ฉ์๋๋ฅผ ์ง์ ํด ์ฃผ๋ ํค๋
Access-Control-Request-Methods : GET, POST, PUT, DELETE
# ์์ฒญ์ ํ์ฉํ๋ ํด๋.
Access-Control-Allow-Headers : Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization
# ํด๋ผ์ด์ธํธ์์ preflight ์ ์์ฒญ ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํ ๊ธฐ๊ฐ์ ์ง์
# 60์ด ๋์ preflight ์์ฒญ์ ์บ์ํ๋ ์ค์ ์ผ๋ก, ์ฒซ ์์ฒญ ์ดํ 60์ด ๋์์ OPTIONS ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ ์๋น ์์ฒญ์ ๋ณด๋ด์ง ์๋๋ค.
Access-Control-Max-Age : 60
# ํด๋ผ์ด์ธํธ ์์ฒญ์ด ์ฟ ํค๋ฅผ ํตํด์ ์๊ฒฉ ์ฆ๋ช
์ ํด์ผ ํ๋ ๊ฒฝ์ฐ์ true.
# ์๋ฐ์คํฌ๋ฆฝํธ ์์ฒญ์์ credentials๊ฐ include์ผ ๋ ์์ฒญ์ ๋ํ ์๋ต์ ํ ์ ์๋์ง๋ฅผ ๋ํ๋ธ๋ค.
Access-Control-Allow-Credentials : true
# ๊ธฐ๋ณธ์ ์ผ๋ก ๋ธ๋ผ์ฐ์ ์๊ฒ ๋
ธ์ถ์ด ๋์ง ์์ง๋ง, ๋ธ๋ผ์ฐ์ ์ธก์์ ์ ๊ทผํ ์ ์๊ฒ ํ์ฉํด์ฃผ๋ ํค๋๋ฅผ ์ง์
Access-Control-Expose-Headers : Content-Length
Node.js ์ธํ
์๋ฒ์ response ํค๋(Header) ๊ฐ์ผ๋ก Access-Control ์ค์ ์ ํด์ค๋ค.
var http = require('http');
const PORT = process.env.PORT || 3000;
var httpServer = http.createServer(function (request, response) {
// Setting up Headers
response.setHeader('Access-Control-Allow-origin', '*'); // ๋ชจ๋ ์ถ์ฒ(orogin)์ ํ์ฉ
response.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); // ๋ชจ๋ HTTP ๋ฉ์๋ ํ์ฉ
response.setHeader('Access-Control-Allow-Credentials', 'true'); // ํด๋ผ์ด์ธํธ์ ์๋ฒ ๊ฐ์ ์ฟ ํค ์ฃผ๊ณ ๋ฐ๊ธฐ ํ์ฉ
// ...
response.writeHead(200, { 'Content-Type': 'text/plain' });
response.end('ok');
});
httpServer.listen(PORT, () => {
console.log('Server is running at port 3000...');
});
์ด๋ Access-Control-Allow-origin ํค๋ ๊ฐ์ผ๋ก * ์ ์ฌ์ฉํ๋ฉด ๋ชจ๋ Origin์์ ์ค๋ ์์ฒญ์ ํ์ฉํ๋ค๋ ์๋ฏธ์ด๋ฏ๋ก ๋น์ฅ์ ํธํ ์ ์๊ฒ ์ง๋ง, ๋ฐ๊ฟ์ ์๊ฐํ๋ฉด ์ ์ฒด๋ ๋ชจ๋ฅด๋ ์ด์ํ ์ถ์ฒ์์ ์ค๋ ์์ฒญ๊น์ง ๋ชจ๋ ํ์ฉํ๊ธฐ ๋๋ฌธ์ ๋ณด์์ ๋ ํ์ ํด์ง๋ค. ๊ทธ๋ฌ๋ ๊ฐ๊ธ์ ์ด๋ฉด ๊ท์ฐฎ๋๋ผ๋ ๋ค์๊ณผ ๊ฐ์ด ์ถ์ฒ๋ฅผ ์ง์ ๋ช
์ํด์ฃผ๋๋ก ํ์.
response.setHeader('Access-Control-Allow-origin', 'https://inpa.tistory.com');
Express.js ์ธํ
> npm i cors
const express = require('express')
const cors = require("cors"); // cors ์ค์ ์ ํธ์ํ๊ฒ ํ๋ ํจํค์ง
const app = express();
// ...
app.use(cors({
origin: "https://naver.com", // ์ ๊ทผ ๊ถํ์ ๋ถ์ฌํ๋ ๋๋ฉ์ธ
credentials: true, // ์๋ต ํค๋์ Access-Control-Allow-Credentials ์ถ๊ฐ
optionsSuccessStatus: 200, // ์๋ต ์ํ 200์ผ๋ก ์ค์
}));
// ...
JSP / Servlet ์ธํ
import javax.servlet.*;
public class CORSInterceptor implements Filter {
private static final String[] allowedOrigins = {
"http://localhost:3000", "http://localhost:5500", "http://localhost:5501",
"http://127.0.0.1:3000", "http://127.0.0.1:5500", "http://127.0.0.1:5501"
};
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String requestOrigin = request.getHeader("Origin");
if(isAllowedOrigin(requestOrigin)) {
// Authorize the origin, all headers, and all methods
((HttpServletResponse) servletResponse).addHeader("Access-Control-Allow-Origin", requestOrigin);
((HttpServletResponse) servletResponse).addHeader("Access-Control-Allow-Headers", "*");
((HttpServletResponse) servletResponse).addHeader("Access-Control-Allow-Methods",
"GET, OPTIONS, HEAD, PUT, POST, DELETE");
HttpServletResponse resp = (HttpServletResponse) servletResponse;
// CORS handshake (pre-flight request)
if (request.getMethod().equals("OPTIONS")) {
resp.setStatus(HttpServletResponse.SC_ACCEPTED);
return;
}
}
// pass the request along the filter chain
filterChain.doFilter(request, servletResponse);
}
private boolean isAllowedOrigin(String origin){
for (String allowedOrigin : allowedOrigins) {
if(origin.equals(allowedOrigin)) return true;
}
return false;
}
}
Spring ์ธํ
// ์คํ๋ง ์๋ฒ ์ ์ญ์ ์ผ๋ก CORS ์ค์
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:8080", "http://localhost:8081") // ํ์ฉํ ์ถ์ฒ
.allowedMethods("GET", "POST") // ํ์ฉํ HTTP method
.allowCredentials(true) // ์ฟ ํค ์ธ์ฆ ์์ฒญ ํ์ฉ
.maxAge(3000) // ์ํ๋ ์๊ฐ๋งํผ pre-flight ๋ฆฌํ์คํธ๋ฅผ ์บ์ฑ
}
}
// ํน์ ์ปจํธ๋กค๋ฌ์๋ง CORS ์ ์ฉํ๊ณ ์ถ์๋.
@Controller
@CrossOrigin(origins = "*", methods = RequestMethod.GET)
public class customController {
// ํน์ ๋ฉ์๋์๋ง CORS ์ ์ฉ ๊ฐ๋ฅ
@GetMapping("/url")
@CrossOrigin(origins = "*", methods = RequestMethod.GET)
@ResponseBody
public List<Object> findAll(){
return service.getAll();
}
}
Apache ์ธํ
httpd.conf์์ <IfModule mod_headers.c> ํ๊ทธ ์์ ํค๋ ์ค์ ์ ๋ฃ์ด์ค๋ค. ๋๋ VirualHost ๋ถ๋ถ์ ๋ฃ์ด๋ ๋๋ค.
<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "*"
</IfModule>
Tomcat
ํฐ์บฃ ํด๋ ๊ฒฝ๋ก์ conf/web.xml ํน์ webapps ๋ด์ ํ๋ก์ ํธ ํด๋ ๋ด WEB-INF/web.xml ํ์ผ์ ์๋ ํค๋ ์ค์ ์ ์ถ๊ฐ
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<init-param>
<param-name>cors.allowed.origins</param-name>
<param-value>*</param-value>
</init-param>
<init-param>
<param-name>cors.allowed.methods</param-name>
<param-value>GET,POST,HEAD,OPTIONS,PUT,DELETE</param-value>
</init-param>
<init-param>
<param-name>cors.allowed.headers</param-name>
<param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
</init-param>
<init-param>
<param-name>cors.exposed.headers</param-name>
<param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
</init-param>
<init-param>
<!-- ์ฟ ํค ํต์ ์ ์ํ๋๋ฐ ์ด๊ฑธ true๋ก ํ๋ฉด 4XX ์๋ฒ ์๋ฌ๊ฐ ๋ฌ๋ค -->
<param-name>cors.support.credentials</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>cors.preflight.maxage</param-name>
<param-value>10</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Nginx
nginx.conf ํ์ผ ์์ location / ๋ถ๋ถ์ add_header ๊ฐ์ผ๋ก ํค๋ ์ค์ ์ ์ถ๊ฐ
location / {
root html;
add_header 'Access-Control-Allow-Origin' '*';
index index.html index.htm;
}
AWS (S3 ํธ์คํ )
- S3 ์ฝ์ ๋ฉ๋ด์ ๋ค์ด๊ฐ ๋ฒํท์ ์ ํํ๋ค.
- ๊ถํ(Permissions) ํญ์ ์ ํํ๋ค.
- ๊ต์ฐจ ์ถ์ฒ ๋ฆฌ์์ค ๊ณต์ ์ฐฝ์์ [ํธ์ง] ์ ํํ๋ค.
- ํ ์คํธ ์์์ ์๋ JSON CORS ๊ท์น์ ์ ๋ ฅํ๋ค.
[
{
"AllowedHeaders": [
"Authorization"
],
"AllowedMethods": [
"GET",
"HEAD"
],
"AllowedOrigins": [
"http://www.example.com"
],
"ExposeHeaders": [
"Access-Control-Allow-Origin"
]
}
]
์ถ๊ฐ์ ์ผ๋ก ์์์ผ ํ๋ CORS ์ฌ๋ก
์๋์ ์ธ ํฌ์คํ ๋์ ์ด๊ฒจ๋ด๊ณ ์ฌ๊ธฐ๊น์ง ์คํฌ๋กคํ์ฌ ํ์ตํ ๋ ์๋ถ๋ค๊ป ์กด๊ฒฝ์ ๋ฐ์๋ฅผ ๋ณด๋ธ๋ค. ํ์ง๋ง ๊ฒจ์ฐ๊ฒจ์ฐ CORS ์ ์ฑ ์ ๋ํด ๊ณต๋ถํ๊ณ ํด๊ฒฐ๋ฒ์ ์ ์ฉ ์์ผฐ๋๋ ๋ ๊ณต๋ถํ ๊ฒ ์๋ค๊ณ ํ๋ ๋ฉํ์ด ํ๋ค๋ฆฐ๋ค. ๋ค๋ง ์ด ๋ถ๋ถ์ ์ค๋ฌด์ ์์ฃผ ๋ฐ์ํ๋ ์ํฉ์ ์๋๊ธฐ ๋๋ฌธ์ ์ง๊ธ ๋น์ฅ ํ์ตํ ํ์์ฑ์ ์ ์ผ๋ฉฐ ์กฐ๊ธ ์ฌํ์ ์ธ ๋ด์ฉ์ด๋ผ ๋์ด๋๋ ์์ด, ์๊ฐ์์๋ ๋์ค์ ํ์ตํ๊ธธ ๊ถ์ฅํ๋ค.
CORS์ ๋ณด์ ์ทจ์ฝ์ ๊ฐ์ด๋
๊ฐ๋ง ์๊ฐํด๋ณด๋ฉด, ์๋๋ SOP ์ ์ฑ ์ผ๋ก ๋งํ์ด์ผํ ์ธ๋ถ ๋ฆฌ์์ค๋ค์ด CORS ์ ์ฑ ์ผ๋ก ์ต์ง๋ก ๋ซ์ด์คฌ์ผ๋ ๊ณต๊ฒฉ์ ๊ทธ๋๋ก ๋ ธ์ถ๋๋๊ฑด ๋น์ฐํ ์์์ผ์ง ๋ชจ๋ฅธ๋ค. ์๋ฅผ๋ค์ด CORS ์ ์ฑ ์ ๋ํด ์๋ฒ์์ ๋๋ฌด ์ ์ฐํ๊ฒ ๋ฆฌ์์ค ํ์ฉ ์ค์ ์ ํ๊ฒ ๋ ๊ฒฝ์ฐ, ํด๋น ์น์ดํ๋ฆฌ์ผ์ด์ ์ ํ๋ฆ์ ์ ์ฉํ์ฌ ํ์ธ์ ๊ฐ์ธ ์ ๋ณด๋ฅผ ํดํนํ ์ํ์ฑ์ด ์๊ฒ ๋๋ค.
๋ํ์ ์ธ ์๋ก ํ์ฉํ ๋๋ฉ์ธ๋ค์ ์ค์ ํ๊ธฐ ๊ท์ฐฎ๋ค๊ณ ์์ผ๋ ์นด๋(*)๋ก ํ์น๋๊ฑธ ๋ค ์ ์๋ค. ์ฆ, ์๋ฒ ๊ฐ๋ฐ์๊ฐ CORS ์ ์ฑ ์ ์๋ชป ๊ตฌ์ฑํ ๊ฒฝ์ฐ ์ฌ๊ฐํ ๋ณด์ ์ํ์ด ๋ ์ ์๋ค๋ ๋ง์ด๋ค. ์ด์ ๋ํ ๋ณด์ ๋ฌธ์ ์ ์๋ฐฉ ๊ฐ์ด๋์ ๋ํด ๋ค์ ํฌ์คํ ์ผ๋ก ๋๋ ๋ณธ๋ค.
Chorme PNA CORS ๋ฌธ์
๋ง์ผ ์์์ ๋ณธ ์ผ๋ฐ์ ์ธ CORS ์๋ฌ ๋ฉ์ธ์ง๊ฐ ์๋ ์๋์ ๊ฐ์ ๋๋ค๋ฅธ CORS ์๋ฌ ๋ฉ์ธ์ง๊ฐ ๋์๋ค๋ฉด, ๋ณ๋์ ์ฌ์ค๋ง ์ ๊ทผ(private network access)์ ๋ํ ์ง์์ด ํ์ํ๋ค.
"The request client is not a secure context and the resource is in more-private adress space local"
๊ฐ๋จํ ๋งํ์๋ฉด ๊ณต์ธ ๋คํธ์ํฌ์ ์น์ฌ์ดํธ์์ ์ฌ์ค ๋คํธ์ํฌ์ ๋ฆฌ์์ค๋ฅผ ํธ์ถํ ๋ ์ฌ์ค๋ง ์ ๊ทผ ๊ด๋ จ CORS ์๋ฌ๊ฐ ๋จ๊ฒ ๋๋๋ฐ, ์์ธํ ๋ด์ฉ์ ์๋ ํฌ์คํ ์ ์ฐธ๊ณ ํ๊ธธ ๋ฐ๋๋ค.
๋ธ๋ผ์ฐ์ Cache์ ์ํ CORS ๋ฌธ์
๋ธ๋ผ์ฐ์ ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ด๋ฏธ์ง์ ๊ฐ์ ๊ณ ์ฉ๋ ๋ฆฌ์์ค์ ๋ํด์ ํ๋ฒ ์์ฒญํด์ ์๋ต๋ฐ์ผ๋ฉด ์๋์ผ๋ก cacheํ์ฌ ์ ์ฅํด๋๋๋ค. ๊ทธ๋ฌ๋ฉด ๋์ค์ ๋๊ฐ์ ๋ฆฌ์์ค๋ฅผ ์ฌ์์ฒญํด๋ ์บ์ ์ ์ฅ์์์ ๋์ด๋ค ์ฐ๋ฉด ๋คํธ์ํฌ ํต์ ์์ด ๋น ๋ฅด๊ฒ ๊ฐ์ ธ์ฌ ์ ์์ด ์ฑ๋ฅ ์ ๊ต์ฅํ ์ด์ ์ ์ป๊ธฐ ๋๋ฌธ์ด๋ค.
๊ทธ๋ฌ๋ ์บ์์ ๋ํด์ ๊ธฐ๋ณธ์ ์ธ ์ง์์ด ์๋ ๋ ์๋ถ๋ค์ด๋ผ๋ฉด ์ด ์บ์ ๊ธฐ๋ฅ ๋๋ฌธ์ ์ต์ ๋ฆฌ์์ค ๋ถ์ผ์น ํ์ ๋ฐ ์ฌ๋ฌ๊ฐ์ง ์์ํ ๋ฌธ์ ์ ์ด ์กด์ฌํ๋ค๋ ๋จ์ ์ ์๊ณ ์์ ๊ฒ์ด๋ค. ์ฆ, CORS ์ญ์ ์ด๋ฌํ ์บ์ ๋ฌธ์ ๋๋ฌธ์ ๋ฌ๊ธ์์ด ๋ฐ์๋ ์๋ ์๋ค.
# ์ฐธ๊ณ ์๋ฃ
https://evan-moon.github.io/2020/05/21/about-cors/
https://core-research-team.github.io/2021-04-01/Easy-to-understand-Web-security-model-story-1%28SOP,-CORS%29
https://blog.devgenius.io/cors-34e947046600
https://westsideelectronics.com/cors-and-how-to-fix/
https://blog.bitsrc.io/4-ways-to-reduce-cors-preflight-time-in-web-apps-1f47fe7558