
๐ curl๋ก๋ ์ฌํ๋์ง ์๋ CORS ์๋ฌ, ์์ธ์ ์๋ฒ๊ฐ ์๋๋ผ ๋ธ๋ผ์ฐ์ ์๋ค (with PNA)
๐ TL;DR
- T&A dev ํ๊ฒฝ์์ ํน์ API ํธ์ถ ์ CORS ์๋ฌ๋ก ์คํจํ๋ค.
- ์์ธ์ ์ผ๋ฐ์ ์ธ CORS ๋ฌธ์ ๊ฐ ์๋๋ผ Chrome์ Private Network Access(PNA) ์ ์ฑ ์ด์๋ค.
- dev ํ๊ฒฝ์ API ๋๋ฉ์ธ์ ๋ด๋ถ IP๋ก resolve ๋๋ ๋ฐ๋ฉด, ํธ์ถํ๋ ํ์ด์ง๋ public ์ฌ์ดํธ๋ผ์ ๋ธ๋ผ์ฐ์ ๊ฐ "์ด ์ฌ์ดํธ๊ฐ ๋ก์ปฌ ๋คํธ์ํฌ ๊ธฐ๊ธฐ์ ์ ๊ทผํ๋ ค ํฉ๋๋ค"๋ผ๋ฉฐ ๊ถํ ํ์ธ์ ์๊ตฌํ๊ณ , ๊ทธ ๊ถํ์ด ํ์ฉ๋์ง ์์ ์ํ๋ผ ์์ฒญ์ด ๋งํ๋ค.
- ์ด์ ํ๊ฒฝ์ API ๋๋ฉ์ธ์ด public IP(CDN)๋ก ๋๊ฐ๊ธฐ ๋๋ฌธ์ ์ด ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์๋๋ค — ๋ด๋ถ๋ง์ ์ง์ ๋ ธ์ถํ dev ํ๊ฒฝ ๊ณ ์ ์ ์ด์์๋ค.
์ฆ์
์นดํ ๊ณ ๋ฆฌ ํด๋ฆญํ๋ฉด ์๋ ์๋ํฌ์ธํธ๋ฅผ fetch ํ๋ค.
GET https://api.dev.example.in/category-overview
์ด๋ฏธ ๊ฐ์ ์ฝ๋๋ก ์ ๋์ํ๋ ๋ค๋ฅธ dev ํธ์คํธ(dev-example.com)์์๋ ๋ฌธ์ ๊ฐ ์์๋๋ฐ, ์๋ก ๋ถ์ธ dev-tour.example.com์์๋ง ๋ธ๋ผ์ฐ์ ์ฝ์์ ๋ค์ ์๋ฌ๊ฐ ๋ด๋ค.
Access to fetch at 'https://api.dev.example.in/category-overview'
from origin 'https://dev-tour.example.com' has been blocked by CORS policy:
Permission was denied for this request to access the `local` address space.
Network ํญ์์๋ ์ค์ ์์ฒญ์ด ๋๊ฐ ํ์ ์์ด Response headers (0), (provisional headers are shown) ์ํ๋ก ํ์๋๋ค. ์ด๋ฅผ ํตํด preflight ๋จ๊ณ์์ ๋งํ ์ํ์์ ์ง์ํ ์ ์์๋ค.
์ฒ์ ์์ฌํ ๊ฒ๋ค
- host ํ์ดํธ๋ฆฌ์คํธ ๋ฏธ๋ฑ๋ก?
curl๋ก ๊ฐ๊ฐ Origin/ํค๋๋ฅผ ํฌํจํ์ฌ ์์ฒญํ๋ ์๋ฒ๋ 200์ ๋ฐํํ๊ณ ,Access-Control-Allow-Origin์ ์์ฒญํ origin์ด ์๋ตํ๋ค. ์ด๋ก์จ ํ์ดํธ๋ฆฌ์คํธ ๋ฌธ์ ๋ ์๋๋ผ๋ ๊ฒ์ ํ๋จํ๋ค. - ์ปค์คํ
ํค๋ ๋ฌธ์ ?
Access-Control-Request-Headers์ ์ปค์คํ ํค๋ 2๊ฐ๋ฅผ ๋ช ์ํด์ preflight๋ฅผ ๋ณด๋ด๋ด๋ ์๋ฒ๋ ์ ์์ ์ผ๋ก ํ์ฉ ์๋ต์ ์คฌ๋ค. - ์๋ฒ ์ค์ ์ฐจ์ด?
๋ origin์ผ๋ก ์์ ํ ๋์ผํ preflight๋ฅผ ๋ณด๋ด ๋น๊ตํ์ง๋ง ์๋ฒ ์๋ต์ ๋ค๋ฅด์ง ์์๋ค. ์๋ฒ ์ชฝ ์ค์ ์ฐจ์ด๋ ์๋์๋ค.
curl๋ก๋ ์๋ฌด๋ฆฌ ์ฌํํด๋ ๋ฌธ์ ๊ฐ ๋ํ๋์ง ์์๋๋ฐ ์ด๊ฑด ์ ์ด์ ์๋ฒ ์๋ต์ด ์๋๋ผ ๋ธ๋ผ์ฐ์ ์์ฒด์ ์ ์ฑ
์ด์๊ธฐ ๋๋ฌธ์ด๋ค. curl์ CORS๋ฅผ ๊ฐ์ ํ์ง ์๋ ๋๊ตฌ๋ผ ์ด ๊ณ์ธต์ ๋ฌธ์ ๋ฅผ ์ฌํํ ์ ์์๋ค.
์์ธ — Private Network Access (PNA)
๋ช ๋ฒ์ ์ฝ์ง ๋์, ์ฒ์์ผ๋ก ๋์๊ฐ ๋ธ๋ผ์ฐ์ ์ฝ์ ์๋ฌ ๋ฉ์์ง๋ฅผ ์์ธํ ๋ค์ ์ฝ์ด๋ดค๋ค.
> Permission was denied for this request to access the 'local' address space.
์ด๊ฑด ์ผ๋ฐ์ ์ธ CORS ์๋ฌ ๋ฉ์์ง๊ฐ ์๋์๋ค. Chrome์ ๋ช ๋ ์ ๋ถํฐ public ์น์ฌ์ดํธ๊ฐ ์ฌ์ค/๋ก์ปฌ ๋คํธ์ํฌ ์ฃผ์๋ก ์์ฒญ์ ๋ณด๋ด๋ ๊ฒ์ ๋ํด ๋ณ๋์ ๋ณด์ ๊ณ์ธต์ ์ ์ฉํ๊ณ ์๋ค. ์ด๋ฅผ Private Network Access(PNA)๋ผ ๋ถ๋ฅธ๋ค.
์ฝ๊ฒ ๋งํด, ๊ณต๊ฐ ์ธํฐ๋ท์ ์๋ ์๋ฌด ์น์ฌ์ดํธ๋ ๋ฐฉ๋ฌธํ์ ๋ ๊ทธ ์ฌ์ดํธ๊ฐ ์คํฌ๋ฆฝํธ๋ก ์ฌ๋ฌ๋ถ์ ๊ณต์ ๊ธฐ ๊ด๋ฆฌ ํ์ด์ง(192.168.x.x)๋ ์ฌ๋ด ์๋ฒ(10.x.x.x)์ ์ ๊ทผํ ์ ์๋ค๋ฉด ์ํํ๋ค. ๊ทธ๋์ ๋ธ๋ผ์ฐ์ ๋ ์ด๋ฐ ์์ฒญ์ ๋ณ๋์ ์น์ธ ์ ์ฐจ๋ฅผ ์๊ตฌํ๋ค.
์ค์ ๋ก ์์ฒญํ๋ API ํธ์คํธ์ IP๋ ๋ค์๊ณผ ๊ฐ์๋ค.
$ dig +short api.dev.example.in
...dev.example.in.
10.0.xx.xx
10.0.xx.xx
...
์ฆ, ์ฌ์ค IP(10.0.x.x)๋ก resolve๋๋ค. ๋ฐ๋ฉด dev-tour.example.com์ CDN์ public IP์์ ์๋น๋จ์ ํ์ธํ๋ค.
$ dig +short dev-tour.example.com
cloudflare...
172.xx.x.xxx
162.xxx.xxx.xxx
๋ธ๋ผ์ฐ์ ๋ ์ฃผ์ ๊ณต๊ฐ์ 3๋จ๊ณ๋ก ๋๋๋ค
PNA๊ฐ ๋ณด๋ ๊ฑด "์ฌ์ค IP๋ ์๋๋"๋ผ๋ ์ด๋ถ๋ฒ์ด ์๋๋ค. Chrome์ ์ฃผ์ ๊ณต๊ฐ์ public < private < local 3๋จ๊ณ๋ก ๋๋๊ณ , ์์ฒญ์ ๋ณด๋ด๋ ํ์ด์ง๋ณด๋ค ๋์์ด ๋ ํ์์ ์ธ ๋จ๊ณ๋ก ๊ฐ ๋๋ง ์ฒดํฌ๋ฅผ ๊ฑด๋ค.
public— ์ธํฐ๋ท ์ด๋์๋ ๋๋ฌ ๊ฐ๋ฅํ ์ฃผ์ (CDN, ๊ณต๊ฐ ์๋ฒ ๋ฑ)private— ์ฌ์ค IP ๋์ญ(10.x.x.x,172.16~31.x.x,192.168.x.x)์ ์ํ๋ ์ฃผ์local—localhost,127.0.0.1๋ฑ ๊ทธ๋ณด๋ค ๋ ์ข์ ๋ฒ์
์ด๋ฒ ์ผ์ด์ค๋:
- ์์ฒญ์ ๋ณด๋ธ ํ์ด์ง(
dev-tour.example.com) → CDN์ public IP → public ๋จ๊ณ - ์์ฒญ ๋์ API(
api.dev.example.in) → ์ฌ๋ด ํด๋ฌ์คํฐ ์ฌ์ค IP → private ๋จ๊ณ
public → private๋ก ๊ฐ๋ ์์ฒญ์ด๋ผ ์ ํํ ์ฒดํฌ ๋์์ ๋ค์ด๋ง์๋ค. ๋ง์ฝ ์์ฒญ์ ๋ณด๋ธ ํ์ด์ง๋ ์ฌ๋ด๋ง IP์์ ์๋น๋๊ณ ์์๋ค๋ฉด(private → private), ์์ฒญ์์ ๋์์ ํ์๋๊ฐ ๊ฐ์ผ๋ ์ด ์ฒดํฌ๋ ์ฒ์๋ถํฐ ๋ฐ๋ํ์ง ์์์ ๊ฒ์ด๊ณ , PNA ์ด์๋ ์์์ ๊ฒ์ด๋ค.
๋ธ๋ผ์ฐ์ ๊ฐ "๋ฌผ์ด๋ณด๊ณ " ์๋ฒ๊ฐ "๋ตํด์ผ" ํต๊ณผํ๋ค
์ด ํ๋จ์ preflight ์์ฒญ/์๋ต์ ํน์ ํค๋ ํ ์์ผ๋ก ์ค๊ฐ๋ค. ์ด๋ฆ์ด ๋น์ทํด์ ํท๊ฐ๋ฆฌ๊ธฐ ์ฌ์ด๋ฐ, ์ง๋ฌธ๊ณผ ๋ต๋ณ์ด ์๋ก ๋ค๋ฅธ ํค๋๋ค.
1. ๋ธ๋ผ์ฐ์ → ์๋ฒ (OPTIONS preflight)
Access-Control-Request-Private-Network: true
2. ์๋ฒ → ๋ธ๋ผ์ฐ์ (preflight ์๋ต)
Access-Control-Allow-Private-Network: true
3. ๋ธ๋ผ์ฐ์ : Access-Control-Allow-Private-Network ์๋ต์ ๋ฐ์ผ๋ฉด ์์ ํ๋ค๊ณ ํ๋จํ๊ณ ๊ทธ์ ์์ผ ์ง์ง GET ์์ฒญ์ ๋ณด๋ธ๋ค
์ด๋ฒ ์ด์์์ ์ค์ ๋ก ๋ฒ์ด์ง ์ผ์, ๋ธ๋ผ์ฐ์ ๊ฐ Access-Control-Request-Private-Network ํค๋๋ ์ ์์ ์ผ๋ก ๋ณด๋์ง๋ง ์๋ต์๋ Access-Control-Allow-Private-Network ํค๋๊ฐ ์์๋ ๊ฒ์ด๋ค. ์๋ฒ์์ ์๋ต ํค๋๊ฐ ๋ด๋ ค์ค์ง์์๊ณ , ์ด๋ก์ธํด ๋ธ๋ผ์ฐ์ ๊ฐ ์์ฒญ์ ๋ง์ ๊ฒ์ผ๋ก ๋ณด์๋ค.
์ ๋ค๋ฅธ ์๋น์ค๋ ๋ฌธ์ ์์ด ๋๊ณ ์์๋
๊ฐ์ฅ ํท๊ฐ๋ ธ๋ ๋ถ๋ถ์ด๋ค. ๊ฐ์ ์ฝ๋, ๊ฐ์ API๋ฅผ ํธ์ถํ๋ dev-example.com์ ์ ๋๊ณ ์์๊ธฐ ๋๋ฌธ์, ์ฒ์์ "์๋ฒ๊ฐ origin๋ณ๋ก ๋ค๋ฅด๊ฒ ์ทจ๊ธํ๋?"๋ผ๋ ์๋ชป๋ ๊ฐ์ค์ ์ธ์ ๋ค. ํ์ง๋ง ์๋ฒ ์๋ต์ ์๋ฌด๋ฆฌ ๋น๊ตํด๋ ์ฐจ์ด๊ฐ ์์๋ค.
์ค์ ์์ธ์ ๋ธ๋ผ์ฐ์ ์ชฝ์ ์์๋ค. Chrome์ PNA๋ ์ฌ์ดํธ๋ณ ์ต์ด ์์ฒญ ์ ์ฌ์ฉ์์๊ฒ ๊ถํ์ ํ์ธํ๋ ํ์
์ ๋์ฐ๊ณ , ์ฌ์ฉ์๊ฐ ์น์ธํ๋ฉด ๊ทธ ๊ฒฐ์ ์ด ๋ธ๋ผ์ฐ์ ์ ๋จ๋๋ค. dev-example.com์ ์ค๋ ์จ์จ ๊ฐ๋ฐ์์ ๋ธ๋ผ์ฐ์ ์๋ ์ด๋ฏธ ์ด ๊ถํ์ด ํ์ฉ๋ ์ํ๋ก ๋จ์ ์์์ ๋ฟ, ์๋ฒ๊ฐ ๊ทธ origin์ ํน๋ณ ์ทจ๊ธํ ๊ฒ ์๋์๋ค.
์ด๊ฑธ ํ์ธํ๊ธฐ ์ํด Chrome ์ค์ ์์ ํด๋น ์ฌ์ดํธ์ "๋ก์ปฌ ๋คํธ์ํฌ" ๊ถํ์ ํ์ฉ์ผ๋ก ๋ฐ๊พผ ๋ค ์ฌ์๋ํ๋๋ dev-tour.example.com์์๋ ์ ์์ ์ผ๋ก API๊ฐ ํธ์ถ๋๋ค. ์ด๊ฑธ๋ก ์์ธ์ด PNA์ธ ๊ฒ์ผ๋ก ํ๋จํ๋ค.
์ ์ด์(prod) ํ๊ฒฝ์ ์ด ๋ฌธ์ ๊ฐ ์์๊น?
์ด์ ํ๊ฒฝ์ API ๋๋ฉ์ธ์ ํ์ธํด๋ณด๋ฉด dev์ ๋ค๋ฅด๋ค.
$ dig +short api.example.com
cdn-edge.example.com.
203.0.xxx.xx
203.0.xxx.xx
์ด์ API๋ CDN์ ํตํด public IP๋ก ๋ ธ์ถ๋๋ค. ์ฆ ์ด์ ํธ๋ํฝ์ "public → public" ์์ฒญ์ด๋ผ PNA ์ฒดํฌ ์กฐ๊ฑด ์์ฒด๊ฐ ์ฑ๋ฆฝํ์ง ์๋๋ค. dev ํ๊ฒฝ์์๋ง BFF๊ฐ ๋ด๋ถ๋ง IP๋ก ์ง์ ๋ ธ์ถ๋์ด ์์๊ธฐ ๋๋ฌธ์ ์๊ธด, dev ํ๊ฒฝ ๊ณ ์ ์ ๋ฌธ์ ์๋ค.
ํด๊ฒฐ ๋ฐฉํฅ
ํ๊ฒฝ๋ณ๋ก ์์ฒญ/๋์์ ์ฃผ์ ๊ณต๊ฐ ์กฐํฉ์ด ๋ค๋ฅด๊ธฐ ๋๋ฌธ์, ํ์ํ ์กฐ์น๋ ํ๊ฒฝ๋ณ๋ก ๋ค๋ฅด๋ค.
- dev (๋ด๋ถ๋ง ํ๊ฒฝ): API๊ฐ ์ฌ์ค IP๋ก ๋
ธ์ถ๋ผ ์์ด
public → private์กฐํฉ์ด ๋ฐ์ํ๋ค. ์ด ํ๊ฒฝ์ ์ฌ๋ด ์ธ์๋ง ์ ๊ทผํ๋ ๊ฐ๋ฐ ํ๊ฒฝ์ด๋ฏ๋ก ๋ธ๋ผ์ฐ์ ์ ๋ก์ปฌ ๋คํธ์ํฌ ์ ๊ทผ ๊ถํ์ ํ์ฉํด๋๋ ๋ฐฉ์์ผ๋ก ์ ๋ฆฌํ๋ค.

- ์ด์(prod): API๊ฐ CDN์ ํตํด public IP๋ก ๋
ธ์ถ๋ผ ์์ด
public → public์์ฒญ์ด๋ค. PNA ์ฒดํฌ ์กฐ๊ฑด ์์ฒด๊ฐ ์ฑ๋ฆฝํ์ง ์์ผ๋ฏ๋ก ๋ณ๋ ์กฐ์น๊ฐ ํ์ ์๋ค.
์ ๋ฆฌ
- ์ฆ์: dev ํ๊ฒฝ์ ํน์ ์ ๊ท ํธ์คํธ์์๋ง ๋ฐ์ํ CORS ์๋ฌ๋ก API ํธ์ถ ์คํจ
- ์์ธ: ๋ธ๋ผ์ฐ์ ์ Private Network Access(PNA) — public ์ฌ์ดํธ์์ ์ฌ์ค IP๋ก ๋ ธ์ถ๋ dev API๋ฅผ ํธ์ถํ ๋ ๋ฐ์
- ํท๊ฐ๋ ธ๋ ์ด์ : ๋ธ๋ผ์ฐ์ ์ ์ฑ
์ด๋ผ ์๋ฒ ์ชฝ(
curl, ์๋ต ํค๋ ๋น๊ต)์ผ๋ก๋ ์ฌํ/ํ๋ณ์ด ์ ๋๊ณ , ๋ค๋ฅธ ํธ์คํธ์์ "์ ๋๊ณ ์์๋ค"๋ ๊ฒ๋ ์๋ฒ ์ค์ ์ด ์๋๋ผ ๊ทธ ๋ธ๋ผ์ฐ์ ์ ๋จ์์๋ ๊ณผ๊ฑฐ ๊ถํ ์น์ธ ์ํ์๋ค - ํด๊ฒฐ: dev/qa๋ ๋ด๋ถ๋ง ํ๊ฒฝ์ด๋ฏ๋ก ๋ธ๋ผ์ฐ์ ์ ๋ก์ปฌ ๋คํธ์ํฌ ์ ๊ทผ ๊ถํ์ ํ์ฉ, ์ด์ ํ๊ฒฝ์ API๊ฐ public IP๋ก ๋ ธ์ถ๋์ด ์์ด ์์ฒ์ ์ผ๋ก ์ด ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์์ ๋ณ๋ ์กฐ์น ๋ถํ์
๋๊ธ