์ด์ฌํ ๋ง๋ ํ๋ก์ ํธ๋ฅผ EC2์ ๋์ ์๋ฒ๋ฅผ ํ์ฑํ ์ํค๊ณ ํ๋ก ํธ ์๋์ ๋ก์ปฌ์์ ํ
์คํธ๋ฅผ ํด๋ณด๋ ค ํ๋๋ฐ,
๋ ์ฉ?! ํ์ด์ง์ ๋ฐ์ดํฐ๋ ๋ณด์ด์ง ์๊ณ , ๊ฐ๋ฐ์ ๋๊ตฌ๋ฅผ ์ผ๋ฉด ๋นจ๊ฐ ์๋ฌ๋ค๋ง ๊ฐ๋ํ์ต๋๋ค.
NBA์ ์ฒซ ๋ฐ๊ธธ์ ๋ด๋๋ ์ ์ธ ์ ์๋ฅผ ํ์ํด์ฃผ๋ฉฐ ๋์ ๊ฐ๊ณ ์์ ํฌ๋ฅผ ์ฑ๊ณต์ํค๋ฉฐ "Welcome to NBA"๋ฅผ ์ธ์น๋ ๋ง์ดํด ์กฐ๋ ๋ง๋ฅ, CORS ์๋ฌ๋ ์น๊ฐ๋ฐ์ ํ๋ ๊ฐ๋ฐ์๋ค์๊ฒ ์น๊ฐ๋ฐ ์ธ๊ณ๋ฅผ ํ์ํด์ฃผ๋ฉฐ ๊ฒช๊ฒ๋๋ ํต๊ณผ์๋ก ๊ฐ์ ๋ ์์ ๋๋ค.
ํ๋ก์ ํธ๋๋ CORS์๋ฌ๋ก ์์ฒญ ๊ณ ์์ ํ์ฌ ๊น๋จน์ง ์๊ฒ ๊ณต๋ถ์ ๊ธฐ๋ก์ ํ๋ คํฉ๋๋ค.
๐ค CORS ๋?
CORS ๋ Cross-Origin Resource Sharing ์ด๋ผ๋ ๋ป์ ๋๋ค. ์ด ๋ฌธ์ฅ์ ์ง์ญํ๋ฉด "๊ต์ฐจ ์ถ์ฒ ๋ฆฌ์์ค ๊ณต์ ์ ์ฑ "์ด๋ผ๊ณ ํด์ํ ์ ์๋๋ฐ, ์ฌ๊ธฐ์ ๊ต์ฐจ ์ถ์ฒ๋ผ๊ณ ํ๋ ๊ฒ์ ๋ค๋ฅธ ์ถ์ฒ๋ฅผ ์๋ฏธํ๋ ๊ฒ์ผ๋ก ๋ณด๋ฉด ๋ฉ๋๋ค.
ํ๋์ ๋ฏ์ด์ ํด๋ถํด ๋ณด์ฃ !
Origin(์ถ์ฒ)?
๋จผ์ ์ถ์ฒ(Origin)์ ๋ํด์ ์์๋ณด๊ฒ ์ต๋๋ค!
์ฐ๋ฆฌ๊ฐ ์ด๋ค ์ฌ์ดํธ๋ฅผ ์ ์ํ ๋ ์ธํฐ๋ท ์ฃผ์์ฐฝ์ ์ฐ๋ฆฌ๋ URL์ด๋ผ๋ ๋ฌธ์์ด์ ํตํด ์ ๊ทผํ๊ฒ ๋ฉ๋๋ค.
์ด์ฒ๋ผ URL์ https://domain.com:3000/user?query=name&page=1๊ณผ ๊ฐ์ด ํ๋์ ๋ฌธ์์ด ๊ฐ์ง๋ง, ์ฌ์ค์ ๋ค์๊ณผ ๊ฐ์ด ์ฌ๋ฌ๊ฐ์ ๊ตฌ์ฑ ์์๋ก ์ด๋ฃจ์ด์ ธ ์์ต๋๋ค.
- Protocol : ํ๋กํ ์ฝ( http, https ๋ฑ๋ฑ)
- Host : ์ฌ์ดํธ ๋๋ฉ์ธ
- Port : ํฌํธ ๋ฒํธ(์๋ต๊ฐ๋ฅ)
- Path : ์ฌ์ดํธ ๋ด๋ถ ๊ฒฝ๋ก
- Query string : ์์ฒญ์ key ๊ฐ๊ณผ value ๊ฐ ( = ์ผ๋ก ๊ตฌ๋ถ)
- Fragment : ํด์ ํ๊ทธ
์ด๋ ์ถ์ฒ ์ฆ, Origin ๋ผ๋ ๊ฒ์ Protolcol ๊ณผ Host ๊ทธ๋ฆฌ๊ณ Port ๊น์ง ๋ชจ๋ ํฉ์น, ๋ฆฌ์์ค๊ฐ ์กด์ฌํ๋ ๊ณณ์ ์๋ฏธํ๋ค๊ณ ๋ณด๋ฉด ๋ฉ๋๋ค.
Cross-Origin(๊ต์ฐจ ์ถ์ฒ)๋
๋ค๋ฅธ ์ถ์ฒ๋ฅผ ์๋ฏธํฉ๋๋ค. ํ๋กํ ์ฝ, ํธ์คํธ, ํฌํธ ๋ฒํธ๊ฐ ๊ฐ์ผ๋ฉด ๋์ผ ์ถ์ฒ (Same-Origin), ํ๋๋ผ๋ ๋ค๋ฅด๋ค๋ฉด ๊ต์ฐจ ์ถ์ฒ (Cross- Origin)์ด๋ผ๊ณ ์๊ฐํ๋ฉด ๋ฉ๋๋ค.
- ํ๋กํ ์ฝ (scheme)
- ํธ์คํธ (domain)
- ํฌํธ ๋ฒํธ (port)
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) |
๋์ผ ์ถ์ฒ ์ ์ฑ vs ๊ต์ฐจ ์ถ์ฒ ์ ์ฑ
์ถ์ฒ(Origin)์ ๋ํ ๊ฐ๊ธฐ ๋ค๋ฅธ ์ ์ฑ
์ธ Same Origin ์ ์ฑ
๊ณผ Cross Origin ์ ์ฑ
์ ๋ํด ์์๋ณด์ฃ !
๋จผ์ SOP(Same Origin Policy) ์ ์ฑ
์ ๋จ์ด ๊ทธ๋๋ก ๋์ผํ ์ถ์ฒ์ ๋ํ ์ ์ฑ
์ ๋งํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด SOP ์ ์ฑ
์ '๋์ผํ ์ถ์ฒ์์๋ง ๋ฆฌ์์ค๋ฅผ ๊ณต์ ํ ์ ์๋ค.'๋ผ๋ ๋ฒ๋ฅ ์ ๊ฐ์ง๊ณ ์์ต๋๋ค.
์ฆ, ๋์ผ ์ถ์ฒ(Same-Origin) ์๋ฒ์ ์๋ ๋ฆฌ์์ค๋ ์์ ๋ก์ด ๊ฐ์ ธ์ฌ์ ์์ง๋ง, ๋ค๋ฅธ ์ถ์ฒ(Cross-Origin) ์๋ฒ์ ์๋ ๋ฆฌ์์ค๋ ์ํธ์์ฉ์ด ๋ถ๊ฐ๋ฅํ๋ค๋ ๋ง์ด์ฃ .
SOP ์ ์ฑ ์ด ์๋ ์ด์ ๋ ๋ฐ๋ก ๋ณด์์ ์ํด์์ ๋๋ค. ์ถ์ฒ๊ฐ ๋ค๋ฅธ ๋ ์ดํ๋ฆฌ์ผ์ด์ ์ด ์์ ๋ก์ด ์ํตํ ์ ์๋ ํ๊ฒฝ์ ๊ฝค ์ํํ ํ๊ฒฝ์ ๋๋ค. ๋ง์ผ ์ ์ฝ์ด ์๋ค๋ฉด, ํด์ปค๊ฐ CSRF(Cross-Site Request Forgery)๋ XSS(Cross-Site Scripting) ๋ฑ์ ๋ฐฉ๋ฒ์ ์ด์ฉํด์ ์ฐ๋ฆฌ๊ฐ ๋ง๋ ์ดํ๋ฆฌ์ผ์ด์ ์์ ํด์ปค๊ฐ ์ฌ์ด๋์ ์ฝ๋๊ฐ ์คํํ์ฌ ๊ฐ์ธ ์ ๋ณด๋ฅผ ๊ฐ๋ก์ฑ ์ ์์ต๋๋ค.
์ด๋ฌํ ์ ์์ ์ธ ๊ฒฝ์ฐ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด, SOP ์ ์ฑ ์ผ๋ก ๋์ผํ์ง ์๋ ๋ค๋ฅธ ์ถ์ฒ์ ์คํฌ๋ฆฝํธ๊ฐ ์คํ๋์ง ์๋๋ก ๋ธ๋ผ์ฐ์ ์์ ์ฌ์ ์ ๋ฐฉ์งํ๋ ๊ฒ์ ๋๋ค.
COP(Cross Origin Policy)
๊ต์ฐจ ์ถ์ฒ ์ ์ฑ ์ ์ง์ํ๋ ๊ฒ ์ค์ <img>, <video>, <script>, <link> ํ๊ทธ ๋ฑ์ด ์์ต๋๋ค.
<link> ํ๊ทธ์ href ์์ ๋ค๋ฅธ ์ฌ์ดํธ์ .css ๋ฆฌ์์ค์ ์ ๊ทผํ๋ ๊ฒ์ด ๊ฐ๋ฅ
<img> ํ๊ทธ์ src ์์ ๋ค๋ฅธ ์ฌ์ดํธ์ .png, .jpg ๋ฑ์ ๋ฆฌ์์ค์ ์ ๊ทผํ๋ ๊ฒ์ด ๊ฐ๋ฅ
<script> ํ๊ทธ์ src ์์ ๋ค๋ฅธ ์ฌ์ดํธ์ .js ๋ฆฌ์์ค์ ์ ๊ทผํ๋ ๊ฒ์ด ๊ฐ๋ฅ (type="module" ์์ฑ์ ์ ์ธ)
์ ํ๊ทธ๋ค์ ํผํฉ ์ฝํ ์ธ (Mixed Content - ์น ํ์ด์ง๊ฐ ๋ณด์ ์ฐ๊ฒฐ(HTTPS)์ ํตํด ์ ์ก๋ ๋, ํ์ด์ง ๋ด์ ์ผ๋ถ ์ฝํ ์ธ [์: ์ด๋ฏธ์ง, ๋น๋์ค, ์คํฌ๋ฆฝํธ, ์คํ์ผ์ํธ ๋ฑ]๊ฐ ๋น๋ณด์ ์ฐ๊ฒฐ(HTTP)์ ํตํด ๋ก๋๋๋ ๊ฒฝ์ฐ )๋ ํ์ฉํ๊ธฐ ๋๋ฌธ์ Mixed Content ์๋ฌ ๋ํ ๋ฐ์ํ์ง ์์ต๋๋ค.
SOP(Same Origin Policy)
XMLHttpRequest, Fetch API ์คํฌ๋ฆฝํธ
๊ธฐ๋ณธ์ ์ผ๋ก Same-Origin ์ ์ฑ ์ ๋ฐ๋ฆ ๋๋ค.
์๋ฐ์คํฌ๋ฆฝํธ์์์ ์์ฒญ์ ๊ธฐ๋ณธ์ ์ผ๋ก ์๋ก ๋ค๋ฅธ ๋๋ฉ์ธ์ ๋ํ ์์ฒญ์ ๋ณด์์ ์ ํํฉ๋๋ค. ๋ธ๋ผ์ฐ์ ๋ ๊ธฐ๋ณธ์ผ๋ก ํ๋์ ์๋ฒ ์ฐ๊ฒฐ๋ง ํ์ฉ๋๋๋ก ์ค์ ๋์ด ์๊ธฐ ๋๋ฌธ์ ๋๋ค. (์ฃผ๋ก ์์ ์ ์๋ฒ)
ํผํฉ ์ปจํ ์ธ ๋ํ ํ์ฉํ์ง ์๊ธฐ ๋๋ฌธ์ HTTPS ์์ฒญ ํ๊ฒฝ์์ API ์์ฒญ ๋ํ HTTPS ํ๋กํ ์ฝ๋ก ์์ฒญํด์ผ ํฉ๋๋ค.
๐ถ CORS ๊ต์ฐจ ์ถ์ฒ ๋ฆฌ์์ค ๊ณต์ (Cross-Origin Resource Sharing)
์ด์ฒ๋ผ ๊ต์ฐจ ์ถ์ฒ ๋ฆฌ์์ค ๊ณต์ (Cross-Origin Resource Sharing, CORS)๋ ๋จ์ด ๊ทธ๋๋ก ๋ค๋ฅธ ์ถ์ฒ์ ๋ฆฌ์์ค ๊ณต์ ์ ๋ํ ํ์ฉ/๋นํ์ฉ ์ ์ฑ
์
๋๋ค.
์๋ฌด๋ฆฌ ๋ณด์์ด ์ค์ํ์ง๋ง, ๊ฐ๋ฐ์ ํ๋ค ๋ณด๋ฉด ๊ธฐ๋ฅ์ ์ด์ฉ ์ ์์ด ๋ค๋ฅธ ์ถ์ฒ ๊ฐ์ ์ํธ์์ฉ์ ํด์ผ ํ๋ ์ผ์ด์ค๋ ์์ผ๋ฉฐ, ๋ํ ์ค๋ฌด์ ์ผ๋ก ๋ค๋ฅธ ํ์ฌ์ ์๋ฒ API๋ฅผ ์ด์ฉํด์ผ ํ๋ ์ํฉ๋ ์กด์ฌํฉ๋๋ค. ๋ฐ๋ผ์ ์ด์ ๊ฐ์ ์์ธ ์ฌํญ์ ๋๊ธฐ ์ํด CORS ์ ์ฑ
์ ํ์ฉํ๋ ๋ฆฌ์์ค์ ํํด ๋ค๋ฅธ ์ถ์ฒ๋ผ๋ ๋ฐ์๋ค์ธ๋ค๋ ๊ฒ ์
๋๋ค.
CORS์ ๋์ ๋ฐฉ์
CORS๋ HTTP ํค๋๋ฅผ ์ฌ์ฉํ์ฌ ๋ธ๋ผ์ฐ์ ์ ์๋ฒ ๊ฐ์ ๋ฆฌ์์ค ๊ณต์ ๊ถํ์ ์ค์ ํฉ๋๋ค. CORS ์์ฒญ์ ํฌ๊ฒ ๋จ์ ์์ฒญ(Simple Request) ๊ณผ ์๋น ์์ฒญ(Preflight Request)์ผ๋ก ๋๋ฉ๋๋ค.
๋จ์์์ฒญ(Simple Request)
๋จ์ ์์ฒญ์ ๋ง๊ทธ๋๋ก ์๋น ์์ฒญ(Prefilght)์ ์๋ตํ๊ณ ๋ฐ๋ก ๋ธ๋ผ์ฐ์ ๋ ์๋ฒ์ ํน๋ณํ ์ฌ์ ์์ฒญ ์์ด ๋ฆฌ์์ค๋ฅผ ์์ฒญํฉ๋๋ค. ์๋ฒ๊ฐ ์ด์ ๋ํ ์๋ต์ ํค๋์ Access-Control-Allow-Origin ํค๋๋ฅผ ๋ณด๋ด์ฃผ๋ฉด ๋ธ๋ผ์ฐ์ ๊ฐ CORS์ ์ฑ ์๋ฐ ์ฌ๋ถ๋ฅผ ๊ฒ์ฌํ๋ ๋ฐฉ์์ ๋๋ค.
๋จ์์์ฒญ์ ๋ค์ ์กฐ๊ฑด์ ๋ชจ๋ ๋ง์กฑํด์ผํ๋ ์์ฒญ์ ๋๋ค.
1. HTTP ๋ฉ์๋๊ฐ GET, POST, HEAD ์ค ํ๋์ผ ๊ฒ.
2. HTTP ํค๋๊ฐ ๋ค์ ์ค ํ๋๋ง ํฌํจ๋ ๊ฒ: Accept, Accept-Language, Content-Language, Content-Type (๋จ, Content-Type์ด application/x-www-form-urlencoded, multipart/form-data, text/plain ์ค ํ๋์ผ ๊ฒฝ์ฐ)
๋งค์ฐ ๋จ์ํ ์กฐ๊ฑด๋ค๋ง ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์ ์ ์กฐ๊ฑด์ ๋ชจ๋ ๋ง์กฑ๋์ด ๋จ์ ์์ฒญ์ด ์ผ์ด๋๋ ์ํฉ์ ๋งค์ฐ ๋๋ญ ๋๋ค. ์๋ํ๋ฉด ๋๋ถ๋ถ HTTP API ์์ฒญ์ text/xml ์ด๋ application/json ์ผ๋ก ํต์ ํ๊ธฐ ๋๋ฌธ์ Content-Type ์กฐ๊ฑด์ด ์๋ฐ๋๊ธฐ ๋๋ฌธ์ ๋๋ค. ๊ทธ๋์ ๋๋ถ๋ถ์ API ์์ฒญ์ ๊ทธ๋ฅ ์๋น์์ฒญ์ผ๋ก ์ด๋ฃจ์ด์ง๋๋ค.
์๋น ์์ฒญ(Preflight Request)
๋ธ๋ผ์ฐ์ ๋ ์์ฒญ์ ๋ณด๋ผ๋ ํ๋ฒ์ ๋ฐ๋ก ๋ณด๋ด์ง์์ต๋๋ค. ๋จผ์ ์๋น ์์ฒญ์ ๋ณด๋ด ์๋ฒ์ ์ ํต์ ๋๋์ง ํ์ธํ ํ ๋ณธ ์์ฒญ์ ๋ณด๋
๋๋ค. ์ด๋ ์ถ์ฒ๋ฅผ ๋น๊ตํ๊ฒ ๋ฉ๋๋ค.
์ฆ, ์๋น ์์ฒญ์ ์ญํ ์ ๋ณธ ์์ฒญ์ ๋ณด๋ด๊ธฐ ์ ์ ๋ธ๋ผ์ฐ์ ์ค์ค๋ก ์์ ํ ์์ฒญ์ธ์ง ๋ฏธ๋ฆฌ ํ์ธํ๋ ๊ฒ์
๋๋ค.
์ด๋ ๋ธ๋ผ์ฐ์ ๊ฐ ์๋น์์ฒญ์ ๋ณด๋ด๋ ๊ฒ์ Preflight๋ผ๊ณ ๋ถ๋ฅด๋ฉฐ, ์ด ์๋น์์ฒญ์ HTTP ๋ฉ์๋๋ฅผ GET์ด๋ POST๊ฐ ์๋ OPTIONS๋ผ๋ ์์ฒญ์ด ์ฌ์ฉ๋๋ค๋ ๊ฒ์ด ํน์ง์
๋๋ค.
์๋น ์์ฒญ์ ๋ค์ ์กฐ๊ฑด ์ค ํ๋๋ผ๋ ๋ง์กฑํ๋ ๊ฒฝ์ฐ์ ๋ฐ์ํฉ๋๋ค
1. HTTP ๋ฉ์๋๊ฐ PUT, DELETE ๋ฑ ๋จ์ ์์ฒญ์ ํด๋นํ์ง ์๋ ๊ฒฝ์ฐ
2. ์ปค์คํ ํค๋๊ฐ ํฌํจ๋ ๊ฒฝ์ฐ
3. Content-Type์ด application/json ๋ฑ ๋จ์ ์์ฒญ์ ํด๋นํ์ง ์๋ ๊ฒฝ์ฐ
์ฆ, ๋จ์ ์์ฒญ ์กฐ๊ฑด์ด ์๋ฐ๋๋ ๊ฒฝ์ฐ ๋ชจ๋ ์๋น์์ฒญ์ผ๋ก ๋ณด๋ธ๋ค๊ณ ์๊ฐํ๋ฉด ๋ฉ๋๋ค.
ํนํ ์ํํ๋ API ํธ์ถ ์๊ฐ ๋ง์ผ๋ฉด ๋ง์ ์๋ก ์๋น ์์ฒญ์ผ๋ก ์ธํด ์๋ฒ ์์ฒญ์ ๋ฐฐ๋ก ๋ณด๋ด๊ฒ ๋๋ ๋น์ฉ์ ์ธ ์ธก๋ฉด์์ ๋ญ๋น๊ฐ ๋ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ ๋ธ๋ผ์ฐ์ ์บ์(Cache)๋ฅผ ์ด์ฉํด Access-Control-Max-Age ํค๋์ ์บ์๋ ์๊ฐ์ ๋ช ์ํด ์ฃผ๋ฉด, ์ด Preflight ์์ฒญ์ ์บ์ฑ ์์ผ ์ต์ ํ๋ฅผ ์์ผ์ค ์ ์์ต๋๋ค.
+) ์ธ์ฆ๋ ์์ฒญ (Credentialed Request)
์ธ์ฆ๋ ์์ฒญ์ ํด๋ผ์ด์ธํธ์์ ์๋ฒ์๊ฒ ์๊ฒฉ ์ธ์ฆ ์ ๋ณด(Credential)๋ฅผ ์ค์ด ์์ฒญํ ๋ ์ฌ์ฉ๋๋ ์์ฒญ์
๋๋ค.
์ฌ๊ธฐ์ ๋งํ๋ ์๊ฒฉ ์ธ์ฆ ์ ๋ณด๋ ์ธ์
ID๊ฐ ์ ์ฅ๋์ด์๋ ์ฟ ํค(Cookie) ํน์ Authorization ํค๋์ ์ค์ ํ๋ ํ ํฐ ๊ฐ ๋ฑ์ ์ผ์ปซ์ต๋๋ค.
์ฆ, ํด๋ผ์ด์ธํธ์์ ์ผ๋ฐ์ ์ธ JSON ๋ฐ์ดํฐ ์ธ์๋ ์ฟ ํค ๊ฐ์ ์ธ์ฆ ์ ๋ณด๋ฅผ ํฌํจํด์ ๋ค๋ฅธ ์ถ์ฒ์ ์๋ฒ๋ก ์ ๋ฌํ ๋ CORS์ ์ธ์ฆ๋ ์์ฒญ์ผ๋ก ๋์๋๋ค๋ ๋ง์ด๋ฉฐ, ์ด๋ ๊ธฐ์กด์ ์๋น ์์ฒญ๊ณผ๋ ์ด์ง ๋น์ทํ์ง๋ง, ์ฐจ์ด์ ์ด ์กด์ฌํฉ๋๋ค.
ํด๋ผ์ด์ธํธ ์ธก
๊ธฐ๋ณธ์ ์ผ๋ก ๋ธ๋ผ์ฐ์ ๊ฐ ์ ๊ณตํ๋ ์์ฒญ API ๋ค์ ๋ณ๋์ ์ต์ ์์ด ๋ธ๋ผ์ฐ์ ์ ์ฟ ํค์ ๊ฐ์ ์ธ์ฆ๊ณผ ๊ด๋ จ๋ ๋ฐ์ดํฐ๋ฅผ ํจ๋ถ๋ก ์์ฒญ ๋ฐ์ดํฐ์ ๋ด์ง ์๋๋ก ๋์ด์์ต๋๋ค.
์ด๋ ์์ฒญ์ ์ธ์ฆ๊ณผ ๊ด๋ จ๋ ์ ๋ณด๋ฅผ ๋ด์ ์ ์๊ฒ ํด์ฃผ๋ ์ต์ ์ด ๋ฐ๋ก credentials ์ต์ ์ ๋๋ค.
ํ๋ก ํธ์๋์ ์๋ฐ์คํฌ๋ฆฝํธ์์ credentials: "include" , withCredentials: true ์ค์ ์ ํ๋ฉด ํด๋ผ์ด์ธํธ์ ์๋ฒ๊ฐ ํต์ ํ ๋ ์ฟ ํค์ ๊ฐ์ ์ธ์ฆ ์ ๋ณด ๊ฐ์ ๊ณต์ ํ๊ฒ ๋ค๋ ์ค์ ์ด ๋ฉ๋๋ค.
JWT๋ฅผ ์ด์ฉํ์ฌ ์ฟ ํค๋ก ์๊ฒฉ ์ธ์ฆ์ ํ๋ ๊ฒฝ์ฐ ์ก์ธ์ค ํ ํฐ์ ์์ฒญ ํค๋์ Authorization ํ๋์ ์ค์ ํฉ๋๋ค.
Authorization ํค๋๋ฅผ ํฌํจํ๊ธฐ ๋๋ฌธ์, ๋จ์ ์์ฒญ(simple request)์ด ์๋๋ฉฐ ํ๋ฆฌํ๋ผ์ดํธ ์์ฒญ์ด ๋ฐ์ํฉ๋๋ค.
์๋ฒ์ธก
์๋ฒ์ธก๋ ๋ง์ฐฌ๊ฐ์ง๋ก ์ด๋ฌํ ์ธ์ฆ๋ ์์ฒญ์ ๋ํด ์ผ๋ฐ์ ์ธ CORS ์์ฒญ๊ณผ๋ ๋ค๋ฅด๊ฒ ๋์ํด์ค์ผ ํฉ๋๋ค.
๋ธ๋ผ์ฐ์ ๋ Access-Control-Allow-Credentials: true ๊ฐ ์๋ ์๋ต์ ๊ฑฐ๋ถํฉ๋๋ค.
Access-Control-Allow-Origin ์๋ต ํค๋์ ์์ผ๋ ์นด๋ (*)๋ฅผ ํ์ฉํ์ง ์์ต๋๋ค.
๋ฐ๋ผ์ ์๋ฒ์์ ๊ผญ ์ค์ ์ ํด์ฃผ์ด์ผ ํฉ๋๋ค.
์ด์ ๋ถํด ์ ๊ฐ ์๋ฒ ์ค์ ์ ํตํด CORS ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ๋ฐฉ๋ฒ์ ์๋ ค๋๋ฆฌ๊ฒ ์ต๋๋ค.
๐ช ํด๊ฒฐ ๋ฐฉ๋ฒ
CORS๋ฅผ ๋ํ ๋ ํนํ๋ ์ฃผ์ ํ ์ ์ ์ถ์ฒ๋ฅผ ๋น๊ตํ๋ ๋ก์ง์ ์๋ฒ์์ ๊ตฌํ๋ ๋ก์ง์ด ์๋ ๋ธ๋ผ์ฐ์ ์์ ๊ตฌํ๋ ๋ก์ง์ด๋ผ๋ ์ ์ ๋๋ค. ์ฆ ์๋ฒ์์ ์ฌ๋ฐ๋ฅธ ์๋ต๊ฐ์ ์ฃผ์ง๋ง ๋ธ๋ผ์ฐ์ ๊ฐ ์ถ์ฒ๋ฅผ ๋น๊ตํด์ ๊ต์ฐจ ์ถ์ฒ๋ฉด block์ ์์ผ CORS ์๋ฌ๋ฅผ ๋ฐ์์ํค๋ ๊ฒ์ ๋๋ค.
์ด๊ฑธ ํด๊ฒฐํ๊ธฐ ์ํด์ ์๋ฒ๋จ์์ ๊ต์ฐจ ์ถ์ฒ๋ฅผ ํ์ฉํ๋ ํค๋๋ฅผ ์ถ๊ฐํด์ฃผ์ด์ผ ํฉ๋๋ค.
โจ ๋ฐฉ๋ฒ 1
์๋ฒ์์ ์น ์ปจํผ๊ทธ๋ฅผ ์ค์ ํด์ CORS ์ค์ ์ ํด์ค ์ ์์ต๋๋ค. ์คํ๋ง ์๋ฒ ์ ์ญ์ ์ผ๋ก CORS ์ค์ ํด์ค ์ ์์ต๋๋ค. WebMvcConfigurer ๋ฅผ ํ์ฅํ์ฌ addCorsMappings ๋ฉ์๋๋ฅผ ์ฌ์ ์ํด์ ์ฌ์ฉํ๊ฒ ๋ฉ๋๋ค. CorsRegistry ๋ฅผ ํตํด ์ค์ ํด์ค ์ ์์ต๋๋ค.
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
private static final String[] ALLOW_ORIGINS = {
"http://localhost:5173",
"https://yanabada-******.vercel.app",
"https://www.*******.com"
};
private static final String[] ALLOW_METHODS = {
"GET", "POST", "PATCH", "DELETE", "HEAD", "OPTIONS", "PUT"
};
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(ALLOW_ORIGINS) // ํ์ฉํ ์ถ์ฒ
.allowedMethods(ALLOW_METHODS) // ํ์ฉํ HTTP method
.allowCredentials(true) // ์ฟ ํค ์ธ์ฆ ์์ฒญ ํ์ฉ์ ์ํด์ true๋ก ์ค์ ํด์ค์ผํฉ๋๋ค.
.maxAge(3000); // ์ํ๋ ์๊ฐ๋งํผ pre-flight ๋ฆฌํ์คํธ๋ฅผ ์บ์ฑํ ์ ์์ต๋๋ค.
}
}
โจ ๋ฐฉ๋ฒ2
ํน์ ์ปจํธ๋กค๋ฌ, ํน์ ๋ฉ์์๋ง CORS ์ ์ฉํ๊ณ ์ถ์ ๋ @CrossOrigin ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
@Controller
@CrossOrigin(origins = "http://localhost:5173", methods = RequestMethod.GET) // ํน์ ์ปจํธ๋กค๋ฌ์๋ง CORS ์ ์ฉํ๊ณ ์ถ์๋.
public class customController {
// ํน์ ๋ฉ์๋์๋ง CORS ์ ์ฉ ๊ฐ๋ฅ
@GetMapping("/url")
@CrossOrigin(origins = "http://localhost:5173", methods = RequestMethod.GET)
@ResponseBody
public List<Object> findAll(){
return service.getAll();
}
}
โจ๋ฐฉ๋ฒ 3์ด์, ํธ๋ฌ๋ธ ์ํ ํ ์
์น ์ปจํผ๊ทธ๋ฅผ ์ค์ ํ๊ณ ๋์ ํ๋ก ํธ์๋์ API ํต์ ์ด ์ ์ด๋ฃจ์ด ์ก์ง๋ง, ์คํ๋ง ์ํ๋ฆฌํฐ๋ฅผ ์ค์ ํ์ ๋ ๋ค์ CORS ์๋ฌ๊ฐ ์๊ฒผ์ต๋๋ค. ์๋ง์ ํธ๋ฌ๋ธ ์ํ ์ ํ๊ณ ๋์ ์ป์ ๊ฒฐ๊ณผ ์คํ๋ง ์ํ๋ฆฌํฐ์ ํํฐ์ฒด์ธ์์ CORS ์ค์ ์ ์ํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
๊ทธ๋ผ, ์ ์น ์ปจํผ๊ทธ ์ค์ ์ ํ๋๋ฐ๋ CORS ๋ฌธ์ ๊ฐ ์๊ฒผ๋์ง ๋๋ฌผ์ ์ ์์ต๋๋ค.
์คํ๋ง ์ํ๋ฆฌํฐ์ CORS ์ค์ ์ ํํฐ ์ฒด์ธ ๋ด์์ CORS ํํฐ๋ฅผ ํตํด ์ด๋ฃจ์ด์ง๋๋ค. Spring Web MVC CORS ์ค์ ์ CorsRegistry ์ฌ์ฉํ์ฌ ์ค์ ํ๋ฉฐ, ์ด๋ ์ธํฐ์ ํฐ์ ์ ์ฌํ ๋ฐฉ์์ผ๋ก ์๋ํฉ๋๋ค. ์ฃผ๋ก ์ปจํธ๋กค๋ฌ์ ๋ํ ์์ฒญ์ ๋ํด CORS ํค๋๋ฅผ ์ถ๊ฐํฉ๋๋ค.
ํํฐ๋ ์๋ธ๋ฆฟ ๊ธฐ๋ฐ์ผ๋ก ์คํ๋ง ์ปจํ ์ด๋์ ์์ฒญ์ด ๋ฟ๊ธฐ ์ ์ ์๋ธ๋ฆฟ ์์ ์์ฒญ์ ์ ์ฒ๋ฆฌ์ ํ์ฒ๋ฆฌ๋ฅผ ํด์ค ์ ์๊ณ , CorsRegistry ์ ์ธํฐ์ ํฐ์ ๋น์ทํ๊ฒ ๋์ํ๋ฏ๋ก ์ปจํธ๋กค๋ฌ ์๋จ์์ ์ ์ฒ๋ฆฌ ํ์ฒ๋ฆฌ๋ฅผ ํฉ๋๋ค.
์์ธํ ๊ฑด ์ , ํํฐ์ ์ธํฐ์ ํฐ ์ฐจ์ด ๋ธ๋ก๊ทธ ๊ธ์ ๋ด์ฃผ์ธ์!
https://programmer-may.tistory.com/198
ํํฐ์ ์ธํฐ์ ํฐ์ ์ฐจ์ด
ํํฐ์ ์ธํฐ์ ํฐ๋ ๋ ๋ค ์น ์ ํ๋ฆฌ์ผ์ด์ ์์ ์์ฒญ๊ณผ ์๋ต์ ์ฒ๋ฆฌํ๋ ๊ณผ์ ์์ ํน์ ์์ ์ ์ํํ๊ธฐ ์ํด ์ฌ์ฉ๋๋ค. ์๋ฐ ํํฐ์ ์คํ๋ง ์ธํฐ์ ํฐ๋ ๋ชจ๋ ํด๋ผ์ด์ธํธ์ ์์ฒญ์ด ์ปจํธ๋กค๋ฌ์
programmer-may.tistory.com
CORS ์ค์ ์ ์์ ๋งํ๋ค์ถ์ด ์๋ฒ์์ ํน์ ํค๋๋ค์ ์ถ๊ฐํ๋ ๊ฒ์ ๋๋ค. Spring Web MVC์ Spring Security์์์ CORS ์ค์ ์ ์ฃผ๋ก ์๋ต ํค๋๋ฅผ ์ค์ ํ๋ ๊ฒ์ผ๋ก, ์ด๋ ์์ฒญ์ ํ์ฒ๋ฆฌ ๊ณผ์ ์ ํด๋นํฉ๋๋ค.
Spring Web MVC์์ CORS ์ค์ ์ ํ๋๋ผ๋ Spring Security ์ค์ ์์ CORS๋ฅผ ๋ช ์์ ์ผ๋ก ์ค์ ํ์ง ์์ผ๋ฉด, Spring Security์ ํํฐ ์ฒด์ธ์์ CORS ์์ฒญ์ด ๊ฑธ๋ฌ์ง ์ ์์ต๋๋ค. ์ด๋ Spring Security๊ฐ ๋ชจ๋ HTTP ์์ฒญ์ ๊ฐ๋ก์ฑ๊ณ ์ฒ๋ฆฌํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
SecurityConfig ์ค์ (์คํ๋ง๋ถํธ 3๋ฒ์ , ์คํ๋ง ์ํ๋ฆฌํฐ 6๋ฒ์ ์ ์ฌ์ฉํ์์ต๋๋ค.)
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthFilter jwtAuthFilter;
private final JwtExceptionFilter jwtExceptionFilter;
private final Oauth2UserService oauth2UserService;
private final Oauth2LoginSuccessHandler oauth2LoginSuccessHandler;
private final Oauth2LoginFailureHandler oauth2LoginFailureHandler;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private static final String[] PERMIT_PATHS = {
"/auth", "/auth/**", "/login/**",
"/oauth2/**", "/signin/**", "/error/**",
"/ws-stomp", "/ws-stomp/**"
};
private static final String[] PERMIT_PATHS_POST_METHOD = {
"/v1/accommodations/**", "/v1/orders",
"/v1/accommodations"
};
private static final String[] PERMIT_PATHS_GET_METHOD = {
"/v1/products", "/v1/products/**",
"/ws-stomp", "/ws-stomp/**",
"/v1/payments/admin"
};
private static final String[] ALLOW_ORIGINS = {
"http://localhost:5173",
"https://yanabada-********.vercel.app",
"https://www.*******.com"
};
private static final String[] ALLOW_METHODS = {
"GET", "POST", "PATCH", "DELETE", "HEAD", "OPTIONS", "PUT"
};
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.httpBasic(AbstractHttpConfigurer::disable);
http.cors(cors -> cors.configurationSource(corsConfigurationSource())); // CORS ํํฐ ์ค์
http.csrf(AbstractHttpConfigurer::disable);
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers(PERMIT_PATHS).permitAll()
.requestMatchers(POST, PERMIT_PATHS_POST_METHOD).permitAll()
.requestMatchers(GET, PERMIT_PATHS_GET_METHOD).permitAll()
.requestMatchers("/v1/products/own").denyAll()
.anyRequest().authenticated()
);
http.exceptionHandling(exceptionHandling -> {
exceptionHandling.authenticationEntryPoint(jwtAuthenticationEntryPoint);
});
http.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(
userInfoEndpoint -> userInfoEndpoint.userService(oauth2UserService))
.successHandler(oauth2LoginSuccessHandler)
.failureHandler(oauth2LoginFailureHandler)
);
http.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(jwtExceptionFilter, JwtAuthFilter.class);
return http.build();
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of(ALLOW_ORIGINS));
configuration.setAllowedMethods(List.of(ALLOW_METHODS));
configuration.setAllowedHeaders(List.of("*"));
configuration.addExposedHeader("Authorization");
configuration.setAllowCredentials(true); //์ฟ ํค๋ฅผ ์ด์ฉํ ์ธ์ฆ ์์ฒญ์ ํ์ฉ
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
๐ง๐ป ๋ง๋ฌด๋ฆฌ ํ๋ฉฐ
CORS ๋ฅผ ๋ง๋๋ฉด์ ๋๋ ๋๋์ด ์น ๊ฐ๋ฐ์๊ตฌ๋ ๋๊ปด๋ณผ ์ ์์์ต๋๋ค. ๋ธ๋ผ์ฐ์ ์์ CORS์ ๋ํ ๋์ ๋ฐฉ์์ด ์ด๋ป๊ฒ ํํด์ง๋์ง, ์ด๊ฑธ ํด๊ฒฐํ๊ธฐ ์ํด์ ์ด๋ค ํค๋๋ฅผ ์ค์ ํด์ค์ผํ๋์ง ๋ฐฐ์ ์ต๋๋ค. ๋๋ถ๋ถ์ API ์์ฒญ์ CORS ๋์ ๋ฐฉ์์ ์๋น์์ฒญ(Preflight) ๊ฑธ ํ์ตํ ์ ์์๊ณ , ์ด๋ป๊ฒ ์๋น์์ฒญ์ด ์ค๋์ง ๋ฐฐ์ธ ์ ์์์ต๋๋ค. ๋ํ ํ๋ก์ ํธ์์ JWT๋ ์ฌ์ฉํ์ฌ, ์ธ์ฆ ์์ฒญ ๋ฐฉ์๋ ๋ฐฐ์ธ ์ ์์์ต๋๋ค.
๊ฒฐ๊ตญ CORS ์๋ฌ๋ ์๋ฒ์์ ํค๋ ์ค์ ์ ํด์ฃผ์ง ์์ ์ผ์ด๋๋ ๊ฒ์ด์์ต๋๋ค.
์๋ฒ์์ ํค๋ ์ค์ ์ ํ๋ ์ผ์ด์ค๋ ์ฌ๋ฌ๊ฐ์ง๊ฐ ์์์ต๋๋ค. ์ ์ญ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ WebMvcConfigurer ๋ฅผ ๊ตฌํํ ์น ์ปจํผ๊ทธ ๋ฐฉ๋ฒ, ํน์ ๋ฉ์๋, ์ปจํธ๋กค๋ฌ์๋ง ์ค์ ํ ์ ์๋ @CrossOrigin ์ด๋ ธํ ์ด์ ๋ง์ง๋ง์ผ๋ก, ์คํ๋ง ์ํ๋ฆฌํฐ๋ฅผ ๋์ ํ์ ์ CORS ํํฐ์์ CORS ์ค์ ํ๋ ๋ฐฉ๋ฒ ๋ฑ์ ํ์ตํ ์ ์์์ต๋๋ค.
๊ทธ์ค ์น ์ปจํผ๊ทธ ์ค์ ์ ํด๋์๋๋ฐ, ์ ์ํ๋ฆฌํฐ๋ฅผ ํ์ฑํ ์ํค๋ฉด ๋ค์ CORS ์๋ฌ๊ฐ ๋ฐ์ํ๋ ์ด์ ์ ๋ํด ํธ๋ฌ๋ธ ์ํ ํ๋ฉฐ ๋ฐฐ์ด ๊ฒ์ด ๊ธฐ์ต์ ๋จ์ต๋๋ค. ์น ์ปจํผ๊ทธ ์ค์ ๋ ํด์คฌ๋๋ฐ CORS๊ฐ ํฐ์ก์ ๋ ํ๋ก ํธ์ํธ ์ธก์์ ์๋ชปํ ๊ฒ์ธ์ค ์๊ณ ์์์ง๋ง, ๊ทธ๋ ์ง ์๋ค๋ ๊ฑธ ๊นจ๋ซ๊ณ ๋ ๋งค์ฐ ๋นํฉํ๋ ๊ธฐ์ต์ด ๋ฉ๋๋ค.
CORS๋ ์ ๊ฐ ์ค๋ช ํ ๊ฒ๋ณด๋ค ๋ ๊น๊ณ , ๋ ์ฌ๋ฌ ์ ๋ก์ฌํญ์ ๋ง๋ ๋ค๊ณ ๋ดค์ต๋๋ค. ๋์ค์ ์ํด์๋ผ๋ ์กฐ๊ธ ๋ ๊น๊ฒ ๊ณต๋ถํด๋ณด๊ณ ์ถ์ต๋๋ค.
'Web programming' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
NGINX ๋ฆฌ๋ฒ์ค ํ๋ก์ ์๋ฒ ์ค์ ํธ๋ฌ๋ธ ์ํ (0) | 2024.02.27 |
---|---|
SSL Termination ์ ์ํด NginX ๋ก ๋ฆฌ๋ฒ์ค ํ๋ก์ ์๋ฒ ๊ตฌ์ถํ๊ธฐ (0) | 2024.02.20 |
Web programming : RESTful URL ์ค๊ณ ๊ท์น (0) | 2023.12.13 |
Web programming : RESTful API ๋ (0) | 2023.12.12 |
WAS (Web Application Server)์ WS (Web Server)์ ์ฐจ์ด (0) | 2023.10.24 |
๋๊ธ