๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

WEB

[Indp] CORS ์—๋Ÿฌ ํ•ด๊ฒฐํ•˜๊ธฐ

๐Ÿš€ ๊ฐœ์š”

Indp ํ”„๋กœ์ ํŠธ์—์„œ ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ๋ฅผ ์—ฐ๋™ํ•˜๋ฉด์„œ CORS ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์˜€๋‹ค. ํด๋ผ์ด์–ธํŠธ๋Š” AWS CloudFront, ์„œ๋ฒ„๋Š” AWS EC2 ๋กœ ๋ฐฐํฌํ•ด ๋†“์€ ์ƒํƒœ์˜€๋‹ค. CORS ์—๋Ÿฌ๋ž€ ์™œ ๋ฐœ์ƒํ•˜๊ณ  ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•˜๋Š”์ง€ ํ•™์Šตํ•ด๋ณด๊ณ  ์ ์šฉํ•ด๋ณด์ž.

๐Ÿ“– CORS

Cross-Origin Resource Sharing ๋ฅผ ์˜๋ฏธํ•˜๋ฉฐ ์š”์ฒญ ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋‹ค๋ฅธ ์ถœ์ฒ˜๋ฅผ ๊ฐ–๋Š” ์„œ๋ฒ„๋กœ ์š”์ฒญ์ด ๊ฐˆ ๋•Œ ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ณด์•ˆ ์ •์ฑ…์ด๋‹ค. ์—ฌ๊ธฐ์„œ ํฌ์ธํŠธ๋Š” ๋‹ค๋ฅธ ์ถœ์ฒ˜, ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋ฐœ์ƒ, ๋ณด์•ˆ ์ •์ฑ…์ด๋‹ค. ๊ฐ๊ฐ์ด ์˜๋ฏธํ•˜๋Š” ๊ฒƒ์ด ๋ฌด์—‡์ธ์ง€ ์•Œ์•„๋ณด์ž.

๋‹ค๋ฅธ ์ถœ์ฒ˜

URL ์—์„œ ์ถœ์ฒ˜๋ž€ ํ”„๋กœํ† ์ฝœ, ํ˜ธ์ŠคํŠธ, ํฌํŠธ ๋ฒˆํ˜ธ๋ฅผ ์˜๋ฏธํ•˜๊ณ  ์ด ์ค‘ ๋™์ผํ•˜์ง€ ์•Š์€ ๊ฒƒ์ด ์žˆ๋‹ค๋ฉด ์ถœ์ฒ˜๊ฐ€ ๋‹ค๋ฅธ ๊ฒƒ์ด๋‹ค. ๋‹ค๋ฅธ ์ถœ์ฒ˜์˜ ์˜ˆ์‹œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • ํ”„๋กœํ† ์ฝœ http โ‰  https
  • ํ˜ธ์ŠคํŠธ www.aaa.com โ‰  www.api.aaa.com
  • ํฌํŠธ ๋ฒˆํ˜ธ www.aaa.com:8080 โ‰  www.aaa.com:3000

๋ธŒ๋ผ์šฐ์ €์—์„œ ๋ฐœ์ƒ

CORS ์—๋Ÿฌ๋Š” ๋ธŒ๋ผ์šฐ์ €์— ์˜ํ•ด ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ์ด๋‹ค. ๋”ฐ๋ผ์„œ ์„œ๋ฒ„ to ์„œ๋ฒ„ ํ†ต์‹ ์—์„œ๋Š” CORS ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.

๋ณด์•ˆ ์ •์ฑ…

์›น ์ƒํƒœ๊ณ„๋Š” SOP(Same-Origin Policy) ๋ผ๋Š” ์ •์ฑ…์ด ์กด์žฌํ•œ๋‹ค. ์ด๋ฆ„์—์„œ๋„ ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด SOP ๋Š” ๊ฐ™์€ ์ถœ์ฒ˜์˜ ๋ฆฌ์†Œ์Šค๋งŒ ๊ณต์œ ํ•˜๋„๋ก ์ œํ•œํ•˜๋Š” ์ •์ฑ…์ด๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ๋‹ค๋ฅธ ์ถœ์ฒ˜์˜ ๋ฆฌ์†Œ์Šค๋ฅผ ์ œํ•œํ•˜๋Š” ์ด์œ ๋Š” ๋ฌด์—‡์ผ๊นŒ?

๋ธŒ๋ผ์šฐ์ €์—๋Š” ํ† ํฐ๊ณผ ๊ฐ™์€ ์ธ์ฆ ์ •๋ณด๋“ค์ด ์ €์žฅ๋  ์ˆ˜ ์žˆ๋‹ค. ์ด ์ธ์ฆ ์ •๋ณด๋“ค์€ ํ•ด๋‹น ์‚ฌ์ดํŠธ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ๋งˆ๋‹ค ํ•จ๊ป˜ ๋ณด๋‚ด์ง„๋‹ค. ์—ฌ๊ธฐ์„œ ๋ฌธ์ œ๋Š” ์‹ ๋ถ„์ฆ๊ณผ ๊ฐ™์€ ์ค‘์š”ํ•œ ์ธ์ฆ ์ •๋ณด๊ฐ€ ๋ธŒ๋ผ์šฐ์ €์— ์ €์žฅ๋œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ๋งŒ์•ฝ ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ์„ ํ•˜์—ฌ ํ† ํฐ์ด ๋ธŒ๋ผ์šฐ์ €์— ์ €์žฅ๋œ ์ƒํƒœ์—์„œ ํ”ผ์‹ฑ ๋ฉ”์ผ์ด๋‚˜ ์•…์„ฑ ์‚ฌ์ดํŠธ๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž์˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์•…์˜์ ์ธ ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋˜๊ฒŒ ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž. SOP ์ •์ฑ…์ด ์—†๋‹ค๋ฉด ์„œ๋กœ ๋‹ค๋ฅธ ์ถœ์ฒ˜ ๋ผ๋ฆฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›์„ ์ˆ˜ ์žˆ๊ฒŒ ๋˜๊ณ  ๋ธŒ๋ผ์šฐ์ €์— ์ €์žฅ๋œ ํ† ํฐ์œผ๋กœ ํ•ด๋‹น ์‚ฌ์ดํŠธ์—์„œ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ์กฐํšŒ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋  ๊ฒƒ์ด๋‹ค. ์ด์™€ ๊ฐ™์€ ์œ„ํ—˜์„ ๋ฐฉ์ง€ํ•˜๋Š” ๊ฒƒ์ด SOP ์ •์ฑ…์ด๋‹ค.

๐Ÿ“– CORS ์š”์ฒญ์˜ ์ข…๋ฅ˜

Simple Request

  • GET, POST, HEAD ๋ฉ”์†Œ๋“œ ์ค‘ ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์š”์ฒญ
  • 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 ์ค‘ ํ•˜๋‚˜์ผ ๊ฒฝ์šฐ
  1. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋‹ค๋ฅธ ์ถœ์ฒ˜๋กœ ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์š”์ฒญ ๋ฉ”์‹œ์ง€์— Origin ํ—ค๋” (ํ˜„์žฌ ์‚ฌ์ดํŠธ์˜ ํ”„๋กœํ† ์ฝœ + ํ˜ธ์ŠคํŠธ + ํฌํŠธ)๋ฅผ ์ถ”๊ฐ€
  2. ์„œ๋ฒ„๋Š” Access-Control-Allow-Origin ํ—ค๋”์— ํ—ˆ์šฉํ•˜๋Š” Origin ์„ ๋ช…์‹œ
  3. ์‘๋‹ต์„ ๋ฐ›์€ ๋ธŒ๋ผ์šฐ์ €๋Š”
    1. Access-Control-Allow-Origin ๊ฐ’์ด ์š”์ฒญ์„ ๋ณด๋‚ธ Origin ๊ณผ ๋‹ค๋ฅด๋ฉด ์‘๋‹ต์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ๋ฒ„๋ฆฌ๊ณ  CORS ์—๋Ÿฌ ๋ฐœ์ƒ
    2. Access-Control-Allow-Origin ๊ฐ’์ด ์š”์ฒญ์„ ๋ณด๋‚ธ Origin ๊ณผ ๊ฐ™๋‹ค๋ฉด ์‘๋‹ต ๋ฐ์ดํ„ฐ๋ฅผ ํด๋ผ์ด์–ธํŠธ๋กœ ์ „๋‹ฌ

Preflight Request

  • Simple Request ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜์ง€ ์•Š๋Š” ์š”์ฒญ

1. ๋ธŒ๋ผ์šฐ์ €๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ์ง„์งœ ์š”์ฒญ์„ ๋ณด๋‚ด๊ธฐ ์ „์— OPTIONS ๋ผ๋Š” ๋ฉ”์†Œ๋“œ๋กœ preflight ์š”์ฒญ์„ ๋ณด๋‚ด์„œ CORS ์™€ ๊ด€๋ จํ•˜์—ฌ ์„œ๋ฒ„์—๊ฒŒ ํ•ด๋‹น ์š”์ฒญ์„ ๋ณด๋‚ด๋„ ๋˜๋Š”์ง€ ๋ฌผ์–ด๋ด„

OPTIONS /resource
Access-Control-Request-Method: ์š”์ฒญ์—์„œ ์‚ฌ์šฉ๋  ๋ฉ”์„œ๋“œ
Origin: ์š”์ฒญ์„ ๋ณด๋‚ธ ์ถœ์ฒ˜

2. ์„œ๋ฒ„๋Š” ํ—ˆ์šฉ๋˜๋Š” Origin, Method ๋“ฑ์„ ์‘๋‹ต

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: ํ—ˆ์šฉ๋˜๋Š” ์ถœ์ฒ˜
Access-Control-Allow-Methods: ํ—ˆ์šฉ๋˜๋Š” ๋ฉ”์„œ๋“œ
Access-Control-Max-Age: preflight ์š”์ฒญ์„ ์บ์‹ฑํ•  ์‹œ๊ฐ„(์ดˆ)

3. ์‘๋‹ต์„ ๋ฐ›์€ ๋ธŒ๋ผ์šฐ์ €๋Š” ์‘๋‹ต ํ—ค๋” ๊ฐ’์ด ์š”์ฒญ์„ ๋ณด๋‚ธ Origin ๊ณผ ๋‹ค๋ฅด๋ฉด ์ง„์งœ ์š”์ฒญ์„ ๋ณด๋‚ด์ง€ ์•Š๊ณ  CORS ์˜ค๋ฅ˜ ๋ฐœ์ƒ

Credential Request

  • ํด๋ผ์ด์–ธํŠธ์—์„œ ์„œ๋ฒ„์—๊ฒŒ ์ž๊ฒฉ ์ธ์ฆ ์ •๋ณด๋ฅผ ์‹ค์–ด ์š”์ฒญํ•  ๊ฒฝ์šฐ
  • ์ž๊ฒฉ ์ธ์ฆ ์ •๋ณด ์ฟ ํ‚ค, Authorization ํ—ค๋” ๋“ฑ
  • ํด๋ผ์ด์–ธํŠธ์—์„œ withCredentials ์˜ต์…˜์„ true ๋กœ ์„ค์ •ํ•ด์•ผ ์ธ์ฆ ๊ด€๋ จ ํ—ค๋”๋ฅผ ์š”์ฒญ์— ์‹ค์–ด ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Œ
  • ์„œ๋ฒ„์—์„œ Access-Control-Allow-Credentials ํ—ค๋”๋ฅผ true ๋กœ ์„ค์ •ํ•ด์•ผ ์‘๋‹ต ๋ฐ์ดํ„ฐ๋ฅผ ํด๋ผ์ด์–ธํŠธ๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Œ

๐Ÿ“– CORS ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

CORS ํ•ด๊ฒฐ์ฑ…์€ ์„œ๋ฒ„์˜ ์‘๋‹ต ํ—ค๋”์ธ Access-Control-Allow-Origin ์— ๋‹ฌ๋ ธ๋‹ค. ์„œ๋ฒ„์—์„œ ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์„ ํ—ˆ์šฉํ• ์ง€ ๋ง์ง€ ๊ฒฐ์ •ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
  1. ํด๋ผ์ด์–ธํŠธ์—์„œ ์„œ๋ฒ„๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๋Œ€์‹  ํด๋ผ์ด์–ธํŠธ์—์„œ ํ”„๋ก์‹œ ์„œ๋ฒ„๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ  ํ”„๋ก์‹œ ์„œ๋ฒ„์—์„œ ์„œ๋ฒ„๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๋ฐฉ๋ฒ•
  2. ์„œ๋ฒ„์—์„œ CORS ๊ด€๋ จ ์‘๋‹ต ํ—ค๋”๋ฅผ ์„ค์ •

๐ŸŒž ์ ์šฉ ํ•ด๋ณด๊ธฐ

CORS ์—๋Ÿฌ๋ž€ ๋ฌด์—‡์ด๊ณ  ์™œ ๋ฐœ์ƒํ•˜๋ฉฐ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•˜๋Š” ์ง€๋ฅผ ์•Œ์•„๋ดค๋‹ค. ์ด์ œ ํ”„๋กœ์ ํŠธ์—์„œ ๋ฐœ์ƒํ•œ CORS ์—๋Ÿฌ๋ฅผ ํ•ด๊ฒฐํ•ด๋ณด์ž.

๐Ÿšจ CORS ์—๋Ÿฌ ๋ฐœ์ƒ

ํด๋ผ์ด์–ธํŠธ Origin ์ธ https://verby.co.kr ๊ณผ ์„œ๋ฒ„์˜ Origin์ธ https://api.verby.co.kr ์ด ๋‹ฌ๋ผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด CORS ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์˜€๋‹ค.  ๋ธŒ๋ผ์šฐ์ €๋Š” ์‘๋‹ต ํ—ค๋”์ธ Access-Control-Allow-Credentials ๋ฅผ ๋ณด๊ณ  ํ˜„์žฌ์˜ Origin ์ด ํ—ˆ์šฉ๋œ Origin ์ธ์ง€ ํ™•์ธํ•ด์•ผํ•˜๋Š”๋ฐ ํ•ด๋‹น ํ—ค๋”๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์•„ CORS ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์˜€๋‹ค.

โœ… Nginx ๋ฅผ ํ†ตํ•œ ์‘๋‹ต ํ—ค๋” ์„ค์ •

์„œ๋ฒ„์˜ ์‘๋‹ต ํ—ค๋”์ธ Access-Control-Allow-Origin ์„ 'https://verby.co.kr' ๋กœ ์„ค์ •ํ•˜์—ฌ ๋ธŒ๋ผ์šฐ์ €์—๊ฒŒ https://verby.co.kr ๊ฐ€ ํ˜€์šฉ๋œ Origin ์ž„์„ ์•Œ๋ ค์ฃผ์ž.

location / {
if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' 'https://verby.co.kr';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, DELETE, PUT, PATCH, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'Content-Type';
        add_header 'Access-Control-Max-Age' 86400;

        return 204;
    }

    add_header 'Access-Control-Allow-Origin' 'https://verby.co.kr' always;

    proxy_pass http://localhost:8080/;
}
  1. Preflight Request ์˜ ์‘๋‹ต ํ—ค๋” ์„ค์ • (Method: OPTIONS)
  2. Simple Request ์˜ ์‘๋‹ต ํ—ค๋” ์„ค์ • (always ์„ค์ •์„ ํ•˜์—ฌ ์‹คํŒจํ•œ ์š”์ฒญ์˜ ๊ฒฝ์šฐ์—๋„ cors ์„ค์ •)

Nginx ๋ฅผ ์žฌ์‹œ์ž‘ํ•˜์—ฌ ๋‹ค์‹œ ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด ๋”์ด์ƒ CORS ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.

  1. ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์š”์ฒญ ํ—ค๋”์— ํ˜„์žฌ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” Origin ์ธ 'https://verby.co.kr' ์„ ์ถ”๊ฐ€
  2. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„์— ์š”์ฒญ
  3. ์„œ๋ฒ„๋Š” ์‘๋‹ต ํ—ค๋”์ธ Access-Control-Allow-Origin ์„ 'https://verby.co.kr' ๋กœ ์„ค์ •ํ•˜์—ฌ ํ—ˆ์šฉ๋˜๋Š” Origin ๋ช…์‹œ
  4. ์‘๋‹ต์„ ๋ฐ›์€ ๋ธŒ๋ผ์šฐ์ €๋Š” Access-Control-Allow-Origin ๊ฐ’์„ ํ†ตํ•ด ํ˜„์žฌ Origin ๊ณผ ๋™์ผํ•œ์ง€ ํ™•์ธ
  5. ํ—ˆ์šฉ๋œ Origin ์ž„์„ ํ™•์ธํ•˜๊ณ  ์‘๋‹ต ๋ฐ์ดํ„ฐ๋ฅผ ํด๋ผ์ด์–ธํŠธ๋กœ ์ „๋‹ฌ
  6. ํ†ต์‹  ์„ฑ๊ณต!

2๊ฐœ ์ด์ƒ์˜ Origin ์„ค์ •ํ•˜๊ธฐ

์„œ๋ฒ„๊ฐ€ ํ—ˆ์šฉํ•˜๋Š” Origin ์€ https://verby.co.kr๊ณผ https://www.verby.co.kr์ด๋‹ค. ๋งจ ์ฒ˜์Œ์—๋Š” ๋‹จ์ˆœํ•˜๊ฒŒ ์—ฌ๋Ÿฌ๊ฐœ์˜ ํ—ค๋”๋ฅผ ์„ค์ •ํ•ด์ฃผ์—ˆ๋‹ค.

add_header 'Access-Control-Allow-Origin' 'https://verby.co.kr';
add_header 'Access-Control-Allow-Origin' 'https://www.verby.co.kr';

ํ•˜์ง€๋งŒ, ์š”์ฒญ์„ ๋ณด๋‚ผ ๊ฒฝ์šฐ CORS ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. 

Access-Control-Allow-Origin ํ—ค๋”๋Š” ๋‘ ๊ฐœ(multiple) ์ด์ƒ์ด๋ฉด ์•ˆ๋œ๋‹ค. ์ด๊ฒƒ๋„ CORS ๊ด€๋ จ ์ •์ฑ…์ธ๊ฐ€๋ณด๋‹ค. ๋ฌผ๋ก  ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •ํ•ด๋„ ๋˜์ง€ ์•Š๋Š”๋‹ค.

add_header 'Access-Control-Allow-Origin' 'https://verby.co.kr, https://www.verby.co.kr';

์ฐพ์•„๋ณด๋‹ˆ Nginx ์—๋Š” ๋ณ€์ˆ˜์— ๋™์ ์œผ๋กœ ๊ฐ’์„ ๋Œ€์ž…ํ•  ์ˆ˜ ์žˆ๋Š” map ๋ชจ๋“ˆ์ด๋ผ๋Š”๊ฒŒ ์žˆ์—ˆ๋‹ค. ์ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋™์ ์œผ๋กœ ํ—ˆ์šฉ Origin ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

# nginx.conf
http {
	...
	map $http_origin $allowed_origin {
        default "https://verby.co.kr";
        https://www.verby.co.kr $http_origin;
    }
}

# sites-available/verby.co.kr
location / {
    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' $allowed_origin;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, DELETE, PUT, PATCH, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
        add_header 'Access-Control-Max-Age' 86400;

        return 204;
    }

    add_header 'Access-Control-Allow-Origin' $allowed_origin always;

    proxy_pass http://localhost:8080/;
}