Web programming

CORS ์—๋Ÿฌ ๊ฐœ๋…๊ณผ ํ•ด๊ฒฐ๋ฒ• (Spring MVC, Spring Security)

ํ”„๋กœ๊ทธ๋ž˜๋จธ ์˜ค์›” 2023. 12. 20.

์—ด์‹ฌํžˆ ๋งŒ๋“  ํ”„๋กœ์ ํŠธ๋ฅผ 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๊ณผ ๊ฐ™์ด ํ•˜๋‚˜์˜ ๋ฌธ์ž์—ด ๊ฐ™์ง€๋งŒ, ์‚ฌ์‹ค์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์—ฌ๋Ÿฌ๊ฐœ์˜ ๊ตฌ์„ฑ ์š”์†Œ๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์ด๋ฏธ์ง€ ์ถœ์ฒ˜ : https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-CORS-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-%F0%9F%91%8F

 

  • 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๋Š” ์ œ๊ฐ€ ์„ค๋ช…ํ•œ ๊ฒƒ๋ณด๋‹ค ๋” ๊นŠ๊ณ , ๋˜ ์—ฌ๋Ÿฌ ์• ๋กœ์‚ฌํ•ญ์„ ๋งŒ๋“ ๋‹ค๊ณ  ๋ดค์Šต๋‹ˆ๋‹ค. ๋‚˜์ค‘์„ ์œ„ํ•ด์„œ๋ผ๋„ ์กฐ๊ธˆ ๋” ๊นŠ๊ฒŒ ๊ณต๋ถ€ํ•ด๋ณด๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.

 

๋Œ“๊ธ€