๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ’ป ํ”„๋กœ์ ํŠธ/๐Ÿ‘‘ VIP ์ดˆ๋Œ€์žฅ ๐Ÿ’Œ

[Spring, React, Vite] ์Šคํ”„๋ง๊ณผ ๋ฆฌ์•กํŠธ ์—ฐ๊ฒฐํ•˜๊ธฐ_ React์—์„œ CORS ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ Spring์—์„œ CORS ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•, Security ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

by ._.sori 2025. 12. 17.

 

 

 

์Šคํ”„๋ง๊ณผ ๋ฆฌ์•กํŠธ๋ฅผ ์„œ๋กœ ์—ฐ๊ฒฐํ•  ๊ฒƒ์ด๋‹ค.
๊ฒ€์ƒ‰์„ ํ•˜๋ฉด React์—์„œ ์—ฐ๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ๋งŽ์ด ๋‚˜์˜จ๋‹ค.
๊ทธ๋Ÿฌ๋‚˜! ๋‚˜๋Š” Spring์—์„œ ์—ฐ๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ๊ถ๊ธˆํ•˜๋‹ค.
๊ทธ๋ž˜์„œ ๋‘˜ ๋‹ค ์•Œ์•„๋ณผ ๊ฒƒ์ด๋‹ค.

 

 

 

 

 

1. ํ™˜๊ฒฝ

  • ํ”„๋ก ํŠธ์—”๋“œ → React + Vite 
    React๋ž‘ Spring ์—ฐ๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๊ฒ€์ƒ‰ํ•˜๋ฉด ๋ณดํ†ต CRA ๋ฐฉ์‹์ด ๋งŽ์ด ๋œฌ๋‹ค. CRA๋Š” Create React App์œผ๋กœ ์—ฌ๋Ÿฌ ๋„๊ตฌ๋ฅผ ํ•˜๋‚˜์˜ ๊ถŒ์žฅ ์„ค์ •์œผ๋กœ ํ†ตํ•ฉํ•˜์—ฌ ๊ธฐ์กด์— ๊ฐ–๊ณ  ์žˆ๋˜ React์˜ ๋ฌธ์ œ์ ์„ ํ•ด๊ฒฐํ•˜๊ณ  ์žˆ์—ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๋ฆฌ์•กํŠธ์—์„œ ์‚ฌ์šฉ์„ ์ค‘๋‹จํ–ˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๊ณ  ์•ˆ๋˜๋Š” ๊ฒƒ ๊ฐ™์ง„ ์•Š์€๋ฐ, ๊ณ ์„ฑ๋Šฅ ํ”„๋กœ๋•์…˜ ์•ฑ ๊ตฌ์ถ•์„ ์–ด๋ ต๊ฒŒ ํ•˜๋Š” ๋ช‡๊ฐ€์ง€ ์ œํ•œ์ด ์žˆ์–ด์„œ CRA ์‚ฌ์šฉ์„ ์ค‘๋‹จํ•˜๊ณ  ๊ธฐ์กด ์•ฑ์€ ํ”„๋ ˆ์ž„์›Œํฌ๋‚˜ Vite, Parcel, RSBuild ๊ฐ™์€ ๋นŒ๋“œ ๋„๊ตฌ๋กœ์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ๊ถŒ์žฅํ•œ๋‹ค๊ณ  ๋ฆฌ์•กํŠธ ์ธก์—์„œ ์ ์–ด๋’€๋‹ค. ๊ฑฐ๊ธฐ๋‹ค ๋‚˜๋Š” Vue๋ฅผ Vite๋กœ ํ•ด์„œ React๋„ Vite๋กœ ์„ค์ •ํ–ˆ๋‹ค. ์ด๋•Œ ์ž์‹ ์ด ์„ค์ •ํ•œ ๋นŒ๋“œ ๋„๊ตฌ๋ฅผ ์ž˜ ์•Œ์•„์•ผ ํ•œ๋‹ค. ํ”„๋ก ํŠธ์—์„œ CORS ์„ค์ •์„ ํ•  ๋•Œ, ์–ด๋–ค ๋นŒ๋“œ ๋„๊ตฌ์ธ์ง€์— ๋”ฐ๋ผ ์„ค์ • ๋ฐฉ์‹์ด ์กฐ๊ธˆ์”ฉ ๋‹ฌ๋ผ์ง„๋‹ค. Vite๋กœ ๋นŒ๋“œ ๋„๊ตฌ๋ฅผ ์„ค์ •ํ•˜์ง€ ์•Š์•˜์œผ๋ฉด, ๋ณธ์ธ์˜ ๋นŒ๋“œ ๋„๊ตฌ์— ๋งž๊ฒŒ ์„ค๋ช…์ด ์ ํ˜€์žˆ๋Š” ๋ธ”๋กœ๊ทธ๋ฅผ ์ฐพ๋Š” ๊ฑธ ์ถ”์ฒœํ•œ๋‹ค.

 

- Create React App ์ง€์› ์ข…๋ฃŒ

 

Create React App ์ง€์› ์ข…๋ฃŒ – React

The library for web and native user interfaces

ko.react.dev

 

 

  • ๋ฐฑ์—”๋“œ → spring
    ๋ฐฑ์—”๋“œ๋Š” spring์œผ๋กœ ์“ธ ๊ฒƒ์ด๋‹ค. ํ˜น์‹œ ๋ชฐ๋ผ์„œ ํ”„๋กœ์ ํŠธ์— ๋Œ€ํ•ด ์„ค๋ช…์„ ํ•ด๋ณด๊ฒ ๋‹ค. DB ๊ด€๋ จ๋œ ํ”„๋กœ์ ํŠธ๋ฅผ ํ•ด๋ณธ ์ ์ด ์—†์–ด์„œ DB๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์ž‘์€ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค ์˜ˆ์ •์ด๋‹ค. ๋ธŒ๋žœ๋“œ๋ฅผ ์šด์˜ํ•œ๋‹ค๋Š” ๊ฐ€์ •ํ•˜์—, VIP ์ด์ƒ์ธ ๋“ฑ๊ธ‰์„ ๊ฐ€์ง„ ๊ณ ๊ฐ๋‹˜๋“ค ํ•œ์ •์œผ๋กœ ์—ฐ๋ง ์ดˆ๋Œ€ ์ฝ”๋“œ๋ฅผ ๋ณด๋‚ธ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ VIP ๋“ฑ๊ธ‰ ์ด์ƒ์ธ ๊ณ ๊ฐ๋‹˜๋“ค์„ ๊ด€๋ฆฌํ•˜๋Š” ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด๋‹ค. ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ๊ด€๋ฆฌ์ž๊ฐ€ ๊ณ ๊ฐ์„ ์ถ”๊ฐ€ํ•˜๋ฉด ์Šคํ”„๋ง์—์„œ ๋ฐ›์•„ ์ฒ˜๋ฆฌํ•˜๊ณ  ์ €์žฅํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค. ๊ทธ๋ž˜์„œ ๋ฆฌ์•กํŠธ์™€ ์Šคํ”„๋ง์„ ์—ฐ๊ฒฐํ•ด์•ผํ•œ๋‹ค. 

 

 


 

 

 

 

2. ๋™์ž‘ ํ™”๋ฉด

  • ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ์ €์žฅ์„ ๋ˆ„๋ฅด๋ฉด ๋ฐฑ์—”๋“œ์—์„œ ์ €์žฅ์„ ์ฒ˜๋ฆฌํ•˜๊ณ  ํ”„๋ก ํŠธ์— ์ ์—ˆ๋˜ ์ •๋ณด๊ฐ€ ๋‚˜ํƒ€๋‚œ๋‹ค. ์—ฌ๊ธฐ์„œ ์ €์žฅ์„ ๋ฐฑ์—”๋“œ์—์„œ ์ฒ˜๋ฆฌํ•˜๊ณ ์ž ํ”„๋ก ํŠธ์—”๋“œ์™€ ๋ฐฑ์—๋“œ๋ฅผ ์„œ๋กœ ์—ฐ๊ฒฐํ•ด์•ผํ•˜๋Š” ์ƒํ™ฉ์ด๋‹ค.

 

 

 

 


 

 

 

 

3. React์—์„œ CORS ์„ค์ •ํ•˜๊ธฐ

  • ํ”„๋ก์‹œ ์„ค์ • ์ถ”๊ฐ€ํ•˜๊ธฐ
    ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ์— vite.config.js๋ฅผ ๋งŒ๋“ ๋‹ค. ๋ฃจํŠธ์— ๋งŒ๋“ค์–ด์•ผ ์ธ์‹๊ฐ€๋Šฅํ•˜๋‹ค.
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
    plugins: [react()],
    server: {
        proxy: {
            "/api": {
                target: "http://localhost:8080",
                changeOrigin: true,
                // SSL ์ธ์ฆ์„œ ๋ฌด์‹œํ•˜์—ฌ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์— ์œ ์šฉํ•˜๋‹ค.
                secure: false,
                // ์ƒํ™ฉ์— ๋”ฐ๋ผ ์ถ”๊ฐ€ํ•œ๋‹ค.
                rewrite: (path) => path.replace(/^\/api/, "")
            },
        },
    },
});
์—ฌ๊ธฐ์„œ rewrite๋Š” spring์—์„œ ์–ด๋–ป๊ฒŒ ๋ฐ›๋Š”์ง€์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง„๋‹ค. rewrite: (path) => path.replace(/^\/api/, "")๋Š” ํ”„๋ก์‹œ๊ฐ€ ์š”์ฒญ ๊ฒฝ๋กœ๋ฅผ ๋ฐฑ์—”๋“œ๋กœ ๋ณด๋‚ด๊ธฐ ์ „์— ์‚ด์ง ๋ฐ”๊ฟ”์ฃผ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. ํ”„๋ก ํŠธ์—์„œ fetch("/api/customers");๋กœ ๋ฐฑ์—”๋“œ์— ๊ฒฝ๋กœ๋ฅผ ๋ณด๋‚ผ ๊ฒƒ์ด๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ํ”„๋ก์‹œ๊ฐ€ ๊ฐ€๋กœ์ณ์„œ ๊ฒฝ๋กœ๋ฅผ ์ˆ˜์ •ํ•˜์—ฌ http://localhost:8080/customers๋กœ ๋ฐฑ์—”๋“œ์— ์‹ค์ œ ์š”์ฒญ์ด ๋œ๋‹ค. ์ด๊ฑธ ์จ์•ผํ•˜๋Š” ๊ฒฝ์šฐ๋Š” @RequestMapping("/customers")๋กœ Spring์— ์„ค์ •ํ•ด๋’€์„ ๊ฒฝ์šฐ์ด๋‹ค. ๋งŒ์•ฝ @RequestMapping("/api/customers")๋ผ๊ณ  ์„ค์ •ํ–ˆ๋‹ค๋ฉด ๊ตณ์ด rewrite๋ฅผ ์ ์–ด์ค„ ํ•„์š”๋Š” ์—†๋‹ค.

 

 

  • fetch 
    ์ฝ”๋“œ๋ฅผ ๋ณด๊ธฐ ์ „์— ๋จผ์ € ์•„๋ž˜์— ์žˆ๋Š” fetch์™€ await ๋Œ€ํ•œ ์„ค๋ช…์ด ์ ํžŒ ๋งํฌ์— ๋“ค์–ด๊ฐ€ ๋ณด๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค. 
const response = await fetch("/api/customers", {
                method: "POST",
                headers: {"Content-Type": "application/json"},
                body: JSON.stringify(form),
            });
์ „์ฒด ์ฝ”๋“œ๋ฅผ ์˜ฌ๋ฆฌ์ง„ ์•Š๊ณ  fetch ์ฝ”๋“œ๋งŒ ์˜ฌ๋ฆฌ๊ฒ ๋‹ค. ๋Œ€์ถฉ ์ด๋Ÿฐ ํ๋ฆ„์ด๊ตฌ๋‚˜ ์ •๋„๋กœ ์ฐธ๊ณ ํ•ด์ฃผ๋ฉด ์ข‹๊ฒ ๋‹ค. ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ๋Š”๋ฐ ์–ด๋””์„œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ๋Š”์ง€ ์ด ์ฝ”๋“œ์— ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š”๊ฑด์ง€ ์•„์ง ์ •ํ™•ํ•˜์ง€๊ฐ€ ์•Š๋‹ค. ๋ฌธ์ œ๋ฅผ ์ฐพ์•„ ๊ณ ์นœ ํ›„ ๋‹ค์Œ ํฌ์ŠคํŒ…์— ํ•ด๋‹น ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ์ฝ”๋“œ๋ฅผ ๋” ์ž์„ธํ•˜๊ฒŒ ์˜ฌ๋ฆฌ๊ฒ ๋‹ค.

fetch๋Š” ์‘๋‹ต์ด ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ด์ง€๋ฉด ์ดํ–‰ํ•˜๋Š” promise๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. await๋Š” promise๊ฐ€ ์ดํ–‰ ๋˜๋Š” ๊ฑฐ๋ถ€ ์ฒ˜๋ฆฌ๋  ๋•Œ๊นŒ์ง€ async ํ•จ์ˆ˜์˜ ์‹คํ–‰์ด ์ผ์‹œ ์ค‘๋‹จ๋œ๋‹ค. ์—ฌ๊ธฐ์„œ promise๋Š” ๋น„๋™๊ธฐ ๋ฉ”์„œ๋“œ์—์„œ ๋งˆ์น˜ ๋™๊ธฐ ๋ฉ”์„œ๋“œ์ฒ˜๋Ÿผ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š”๋ฐ, ๋‹ค๋งŒ ์ตœ์ข… ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๊ณ  ๋ฏธ๋ž˜์˜ ์–ด๋–ค ์‹œ์ ์— ๊ฒฐ๊ณผ๋ฅผ ์ œ๊ณตํ•˜๊ฒ ๋‹ค๋Š” ํ”„๋กœ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ๊ฐœ๋…์„ ๋”ฐ๋กœ๋”ฐ๋กœ ์‚ดํŽด๋ณด๋‹ˆ ์ข…ํ•ฉ์ ์œผ๋กœ ์ดํ•ด๊ฐ€ ์ž˜ ๊ฐ€์ง€ ์•Š๋Š”๋‹ค. ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด์„œ ์ตœ๋Œ€ํ•œ ์ดํ•ดํ•ด๋ณด์ž! (์‚ฌ์‹ค ์ œ๋Œ€๋กœ ์ดํ•ดํ•œ๊ฑด์ง€ ์ž˜ ๋ชจ๋ฅด๊ฒ ๋‹น)

์šฐ๋ฆฌ์˜ ๋ชฉํ‘œ๋Š” ๋ฐฑ์—”๋“œ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ , ๋ฐฑ์—”๋“œ์—์„œ ์–ด๋–ค ์ฒ˜๋ฆฌ ์ดํ›„ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜๋ฐ›์•„์•ผ ํ•œ๋‹ค. ๊ทธ๋ž˜์„œ "/api/customers" ๊ฒฝ๋กœ๋กœ ํ—ค๋”์™€ ๋ฐ”๋””๋ฅผ POST ๋ฐฉ์‹์œผ๋กœ ๋ณด๋ƒˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›๋Š” ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฐ๋‹ค. ์™œ๋ƒ๋ฉด ๋„คํŠธ์›Œํฌ,๋น„๋™๊ธฐ ์ž‘์—…์ด๊ธฐ ๋•Œ๋ฌธ์— ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฐ๋‹ค. ์•„๋งˆ ์ด ๋‹ค์Œ ์ฝ”๋“œ๋Š” ๋ฐ˜ํ™˜ ๋ฐ›์€ ๊ฒฐ๊ณผ๋ฅผ ์ด์šฉํ•œ ์ฝ”๋“œ์ผ ๊ฒƒ์ด๋‹ค. ์•„์ง ๊ฒฐ๊ณผ๋Š” ์˜ค์ง€ ์•Š์•˜๋Š”๋ฐ ๋‹ค์Œ ์ฝ”๋“œ๋กœ ๋„˜์–ด๊ฐ€๊ฒŒ ๋œ๋‹ค๋ฉด? ๋ถ„๋ช… ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒƒ์ด๋‹ค. ๊ทธ๋ž˜์„œ fetch๋กœ promise ๋ฐ˜ํ™˜์„ ๋ฐ›๋Š” ๊ฒƒ์ด๋‹ค.(fetch๊ฐ€ promise๋กœ ๋ฐ›์•„์•ผ๊ฒ ๋‹ค๊ณ  ํ•ด์„œ ๋ฐ›๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๊ฐ€ promise๋กœ ๋Œ๋ ค์ฃผ๋Š” ๊ฒƒ ๊ฐ™๋‹ค.) promise๋Š” ์„ธ๊ฐ€์ง€ ์ƒํƒœ๊ฐ€ ์žˆ๋‹ค. ๋Œ€๊ธฐ-์ดํ–‰(์„ฑ๊ณต)-๊ฑฐ๋ถ€(์‹คํŒจ)๊ฐ€ ์žˆ๋‹ค. await๋Š” promise๊ฐ€ ๋Œ€๊ธฐ์—์„œ ์ดํ–‰ ํ˜น์€ ๊ฑฐ๋ถ€๋ผ๋Š” ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ฌ ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋„๋ก ํ•œ๋‹ค.

๋‹ค์‹œ ์ •๋ฆฌํ•ด๋ณด์ž. ๊ฒฐ๊ณผ๊ฐ€ ์˜ค๊ธฐ๊นŒ์ง€ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฐ๋‹ค. ๊ทธ๋ž˜์„œ await๋กœ ๋‹ค์Œ ๊ฒฐ๊ณผ๋กœ ๋„˜์–ด๊ฐ€์ง€ ์•Š๋„๋ก ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋„คํŠธ์›Œํฌ, ๋น„๋™๊ธฐ ์ž‘์—…์€ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์—์„œ promise๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ๊ทธ๋ž˜์„œ promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” fetch๋ฅผ ์ผ๋‹ค.

์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ๊ฒƒ์€ ๊ธฐ๋‹ค๋ฆฌ๊ฒŒ ํ•œ๋‹ค๊ณ  ํ•ด์„œ ํ”„๋กœ๊ทธ๋žจ ์ž์ฒด๊ฐ€ ๋ฉˆ์ถฐ์žˆ๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค. ์‚ฌ์‹ค ํ˜„์žฌ ์ด ์ฝ”๋“œ๋Š” async๊ฐ€ ๋ถ™์€ ํ•จ์ˆ˜ ์•ˆ์— ๋“ค์–ด์žˆ๋Š”๋ฐ, await๊ฐ€ ๋“ค์–ด์žˆ๋Š” async ํ•จ์ˆ˜๋งŒ ๋ฉˆ์ถ˜๊ฑฐ์ง€ ๋‹ค๋ฅธ ๋ถ€๋ถ„์€ ๋Œ์•„๊ฐ„๋‹ค. ์ด ๋ง์ด ์ดํ•ด๊ฐ€ ์ž˜ ์•ˆ ๊ฐˆ ์ˆ˜ ์žˆ๋‹ค. ๊ฐ€์ •์„ ํ•ด๋ณด์ž. ๋„ค๋ชจ๊ฐ€ ์žˆ๊ณ  ์„ธ๋ชจ๊ฐ€ ์žˆ์œผ๋ฉฐ, ์ผ์ • ์‹œ๊ฐ„๋งˆ๋‹ค ๋‘˜ ๋‹ค ์ƒ‰์ƒ์ด ๋ณ€๊ฒฝ๋œ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜์ž. ์ด๋•Œ ๋„ค๋ชจ๋Š” async๊ฐ€ ์“ฐ์ด๋ฉฐ ํ•จ์ˆ˜ ์•ˆ์— await sleep(1000);์ด๋ž€ ์ฝ”๋“œ๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ๋„ค๋ชจ๋Š” ์ผ์ •ํ•œ ์‹œ๊ฐ„์„ ๋ฉˆ์ถ˜ ๋’ค ์ƒ‰์ƒ์ด ๋ณ€๊ฒฝ๋  ๊ฒƒ์ด๋‹ค. ์„ธ๋ชจ๋Š” ๋„ค๋ชจ๊ฐ€ ๋ฉˆ์ถฐ์žˆ๋Š” ๋™์•ˆ์—๋„ ์ƒ‰์ƒ์ด ๋ณ€๊ฒฝ๋  ๊ฒƒ์ด๋‹ค.

 

 

 

- Fetch API

 

Fetch API ์‚ฌ์šฉํ•˜๊ธฐ - Web API | MDN

fetch() ๋ฉ”์„œ๋“œ์—๋Š” ์„ ํƒ์ ์œผ๋กœ ๋‘ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜๋„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋งค๊ฐœ๋ณ€์ˆ˜, init ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์„ค์ •์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. fetch() ๋ฌธ์„œ๋ฅผ ๋ฐฉ๋ฌธํ•ด ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ „์ฒด ์˜ต์…˜์˜

developer.mozilla.org

 

 

- fetch

 

fetch() ์ „์—ญ ํ•จ์ˆ˜ - Web API | MDN

Response ๊ฐ์ฒด๋กœ ์ดํ–‰ํ•˜๋Š” Promise์ž…๋‹ˆ๋‹ค. ์ €ํฌ์˜ ์ทจ๋“ ์š”์ฒญ ์˜ˆ์ œ(๋ฏธ๋ฆฌ๋ณด๊ธฐ)์—์„œ๋Š” ์ƒˆ๋กœ์šด Request ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑ์ž๋กœ ์ƒ์„ฑํ•˜๊ณ , fetch() ํ˜ธ์ถœ๋กœ ์ทจ๋“ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฏธ์ง€๋ฅผ ์ทจ๋“ํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ, ์š”์ฒญ์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ

developer.mozilla.org

 

 

- await

 

await - JavaScript | MDN

ํ”„๋กœ๋ฏธ์Šค ๋˜๋Š” thenable ๊ฐ์ฒด์˜ ์ดํ–‰ ๊ฐ’์ด๊ฑฐ๋‚˜, ํ‘œํ˜„์‹์ด thenable์ด ์•„๋‹Œ ๊ฒฝ์šฐ ํ•ด๋‹น ํ‘œํ˜„์‹์˜ ์ž์ฒด ๊ฐ’. ํ”„๋กœ๋ฏธ์Šค ๋˜๋Š” thenable ๊ฐ์ฒด๊ฐ€ ๊ฑฐ๋ถ€๋˜๋ฉด, ํ•ด๋‹น ๊ฑฐ๋ถ€ ์‚ฌ์œ ๋ฅผ ๋˜์ง‘๋‹ˆ๋‹ค. await๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ Promise

developer.mozilla.org

 

 

 

  • ์‹ค์ œ ๋ฐฐํฌ
    ๊ฐœ๋ฐœ๋งŒ ํ•  ๋•Œ๋Š” ์ด์ •๋„๋งŒ ํ•ด๋„ ์ถฉ๋ถ„ํžˆ ๋™์ž‘์ด ๋œ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋ฐฐํฌํ•  ๋•Œ๋Š” ๊ผญ spring์— CORS๋ฅผ ์ ์šฉํ•ด์•ผํ•œ๋‹ค.

- Spring CORS ์„ค์ •

 

vite-react ์™€ ์Šคํ”„๋ง ๋ถ€ํŠธ ์—ฐ๋™ํ•˜๊ธฐ

start.spring.io ์ ‘์†ํ•ด์„œ ์œ„ ์‚ฌ์ง„๊ณผ ๊ฐ™์ด ๋Œ€์ถฉ ์„ธํŒ… ํ›„ ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.https://nodejs.org/ ์— ๋“ค์–ด๊ฐ€์„œ LTS ๋ฒ„์ „์„ ์„ค์น˜ํ•œ๋‹ค.๋ช…๋ น ํ”„๋กฌํ”„ํŠธ์— node --version ์ž…๋ ฅํ•ด์„œ ์œ„์™€ ๊ฐ™์ด ๋‚˜์˜ค๋ฉด ๋…ธ๋“œ๋Š” ์ •์ƒ์ 

velog.io

 

 

 

 


 

 

 

 

4. Spring์—์„œ CORS ์„ค์ •ํ•˜๊ธฐ

  • vite.config.js
    ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ์— vite.config.js๋ฅผ ๋งŒ๋“ ๋‹ค. ๋ฃจํŠธ์— ๋งŒ๋“ค์–ด์•ผ ์ธ์‹๊ฐ€๋Šฅํ•˜๋‹ค.
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()],
});

 

๋ฆฌ์•กํŠธ์—์„œ CORS ์„ค์ •์„ ํ–ˆ์„ ๋•Œ๋Š” ํ”„๋ก์‹œ๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ spring์—์„œ CORS๋ฅผ ์„ค์ •ํ•  ๊ฒฝ์šฐ ํ”„๋ก์‹œ๊ฐ€ ํ•„์š”์—†๊ธฐ ๋•Œ๋ฌธ์— ์œ„์ฒ˜๋Ÿผ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ ์–ด์ค€๋‹ค. ๋งŒ์•ฝ์— ํ”„๋ก ํŠธ์—”๋“œ์™€ ๋ฐฑ์—”๋“œ์—์„œ CORS๋ฅผ ๋ชจ๋‘ ํ—ˆ์šฉํ•˜๊ฒŒ ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ํ”„๋ก์‹œ๋ฅผ ํฌํ•จํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด ๋œ๋‹ค. ํ”„๋ก์‹œ ํฌํ•จ ์ฝ”๋“œ๋Š” ์œ„์— ์ ์—ˆ๋‹ค.

 

 

  • fetch 
const response = await fetch("http://localhost:8080/api/customers", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(form),
});
์ด ์ฝ”๋“œ์— ๋Œ€ํ•œ ์„ค๋ช…์€ ์œ„์— ํ”„๋ก ํŠธ์—์„œ CORS ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ์ ์–ด๋’€๋‹ค. ๋‘ ์ฝ”๋“œ์— ์ฐจ์ด๊ฐ€ ์žˆ๋‹ค๋ฉด ๊ฒฝ๋กœ์ธ๋ฐ, ํ”„๋ก์‹œ๊ฐ€ ์—†์œผ๋‹ˆ ์ง์ ‘ ๋ฐฑ์—”๋“œ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๊ธฐ ์œ„ํ•ด "http://localhost:8080/api/customers"์œผ๋กœ ์ ์–ด์คฌ๋‹ค.

 

 

  • ๋ฐฉ๋ฒ• 1_ @CrossOrigin์„ ๋ถ™์ธ๋‹ค.
    ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ์ƒํ™ฉ์— ๋”ฐ๋ผ ์“ฐ๋ฉด ๋ฉ๋‹ˆ๋‹ค.
@RestController
@RequestMapping("/api/customers")
@CrossOrigin(origins = "http://localhost:5173", //ํ—ˆ์šฉํ•  ์ฃผ์†Œ
 methods = {RequestMethod.POST}, //POST ๋ฐฉ์‹ ํ—ˆ์šฉ
 allowedHeaders("*")) //๋ชจ๋“  ํ—ค๋” ํ—ˆ์šฉ
public class CustomerController {
...
}
@CrossOrigin์€ CORS๋ฅผ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜์ด๋‹ค. ์ปจํŠธ๋กค๋Ÿฌ ํ˜น์€ ๋ฉ”์„œ๋“œ๋งˆ๋‹ค ์„ค์ •ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ๊ฐ„๋‹จํ•˜๊ฒŒ @CrossOrigin์„ ๋ถ™์ด๋ฉด ๋œ๋‹ค. ์ •๋ฐ€ํ•˜๊ฒŒ ์ ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ์žฅ์ ์ด ์žˆ์œผ๋‚˜, ํ”„๋กœ์ ํŠธ๊ฐ€ ์ปค์ ธ์„œ @CrossOrigin์„ ์ ์„ ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ๋งŽ์•„์ง„๋‹ค๋ฉด ๋ฐ˜๋ณต์ ์ธ ์ฝ”๋“œ๋ฅผ ๋ถ™์—ฌ์•ผ ํ•œ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ๋‹ค.

 

 

  • ๋ฐฉ๋ฒ•2_ WebMvcConfigurer
    ์ „์—ญ์œผ๋กœ CORS๋ฅผ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**") //CORS ์ •์ฑ…์„ ์ ์šฉํ•  URL ๊ฒฝ๋กœ ํŒจํ„ด
                .allowedOrigins("http://localhost:5173") //์š”์ฒญ์„ ํ—ˆ์šฉํ•  ์ฃผ์†Œ
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") //ํ—ˆ์šฉํ•  HTTP ๋ฉ”์„œ๋“œ
                .allowedHeaders("*") //๋ชจ๋“  ํ—ค๋” ํ—ˆ์šฉ
                .allowCredentials(true); // ์ธ์ฆ๋œ ์š”์ฒญ์„ ํ—ˆ์šฉ (์˜ˆ: ์ฟ ํ‚ค, HTTP ์ธ์ฆ)
    }
}
WebMvcConfigurer๋Š” SpringMVC์˜ ๊ธฐ๋ณธ ์„ค์ •์„ ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋„์™€์ฃผ๋Š” ์ธํ„ฐํŽ˜์ด์Šค์ด๋‹ค. ์Šคํ”„๋ง ๋ถ€ํŠธ๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹œ์ž‘ํ•  ๋•Œ, MVC ์„ค์ •์„ ๊ตฌ์ƒํ•˜๋ฉด์„œ ์ž๋™์œผ๋กœ addCorsMappings๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค. ๊ทธ๋ž˜์„œ addCorsMappings ์•ˆ์— CORS๋ฅผ ์ˆ˜์ •ํ•˜์—ฌ registry์— ๋“ฑ๋กํ•˜๋ฉด ๋œ๋‹ค.

addMapping์œผ๋กœ ๊ฒฝ๋กœ๋ฅผ ๋“ฑ๋กํ•˜๊ณ  ์ฒด์ธ์„ ํ†ตํ•ด ๋‹ค๋ฅธ ์„ค์ •๋„ ์ˆ˜์ •ํ•˜๋ฉด CoreRegistry๊ฐ€ ๊ทธ๊ฑธ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”๋‹ค. ๊ทธ๋ ‡๊ฒŒ "/api/**" ๊ฒฝ๋กœ๋Š” ์ด๋Ÿฐ ๊ทœ์น™์„ ๊ฐ€์ง€๋„๋ก ์ ์šฉํ•œ๋‹ค.

์ด๋ ‡๊ฒŒ ์ „์—ญ์œผ๋กœ ๊ด€๋ฆฌํ•˜๋ฉด ์ข‹์€ ์ ์€ ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์‰ฝ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ Spring Security๋ฅผ ์“ฐ๋ฉด ์ด ์„ค์ • ๋ฐฉ์‹์ด ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š์„ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋‹ค.(์ „์— ๊ทธ๋žฌ์–ด์„œ ์—„์ฒญ ์• ๋จน์—ˆ๋‹ค!)  ๊ทธ ์ด์œ ๋Š” Sercurity๋ฅผ ์“ฐ๋ฉด ์š”์ฒญ์ด Security ํ•„ํ„ฐ์— ๋ง‰ํžˆ๊ฒŒ ๋˜์–ด CORS ์š”์ฒญ์ด ์ œ๋Œ€๋กœ ์ „๋‹ฌ๋˜์ง€ ์•Š๋Š”๋‹ค. 

 

 

  • SecurityConfig  ์„ค์ •ํ•˜๊ธฐ
    Security๋ฅผ ํฌํ•จ์‹œ์ผœ์„œ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“  ๊ฒฝ์šฐ ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑํ•ด์ค˜์•ผํ•œ๋‹ค. 
@Configuration
// Spring Security๋ฅผ ํ™œ์„ฑํ™”ํ•œ๋‹ค. ์Šคํ”„๋ง 3.X๋ถ€ํ„ฐ๋Š” ์ƒ๋žต๊ฐ€๋Šฅํ•œ๋“ฏ?
@EnableWebSecurity
public class SecurityConfig {
    // HttpSecurity๋ฅผ ๋ฐ›์•„ ์ธ์ฆ/์ธ๊ฐ€, CORS, CSRF ๋“ฑ ์›น ๋ณด์•ˆ ์ •์ฑ…์„ ์ฝ”๋“œ๋กœ ์„ค์ •ํ•œ๋‹ค.
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf(csrf -> csrf.disable())
                // ์˜ค๋ฅ˜ ๋ฐœ์ƒ์œผ๋กœ ๊ฐœ๋ฐœ๋‹จ๊ณ„์—์„œ ์ „์ฒด ํ—ˆ์šฉ, ์ถ”ํ›„ ์กฐ์ • ๊ฐ€๋Šฅ
                .authorizeHttpRequests(authz -> authz
                        .requestMatchers("/api/register").permitAll()//ํ•ด๋‹น ๊ฒฝ๋กœ๋Š” ๋ˆ„๊ตฌ๋‚˜ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋„๋ก
                        .requestMatchers("/api/login").permitAll()
                        .requestMatchers("/h2-console/**").permitAll()//DB๋„ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋„๋ก
                        .requestMatchers("/oauth/kakao").permitAll()
                        .anyRequest().permitAll() // ๋ชจ๋“  ์š”์ฒญ ์ธ์ฆ ์—†์ด ํ—ˆ์šฉ
                )
                .headers(headers
                        -> headers.frameOptions(frameOptions -> frameOptions.disable()))
                .cors(cors -> {}); //๋‹ค๋ฅธ ์ถœ์ฒ˜์—์„œ API๋ฅผ ํ˜ธ์ถœํ•˜๋„๋ก ํ•œ๋‹ค.
        return http.build(); //์œ„์˜ ์„ค์ •๊ฐ’์„ ํ† ๋Œ€๋กœ SecurityFilterChain ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ด์„œ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•œ๋‹ค.
    }

    // ๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ™”(ํ•ด์‹œ์ฒ˜๋ฆฌ์ธ๋“ฏ) ์‹œํ‚ค๊ธฐ.
    // @AutoWired๋กœ ๋ฐ”๋กœ ์“ธ ์ˆ˜ ์žˆ์Œ
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
์ด๊ฑด ์ „์— ์žˆ๋˜ ํ”„๋กœ์ ํŠธ์—์„œ ์ž‘์„ฑํ•œ SercurityConfig์ด๋‹ค. ํ‰์†Œ์— ๊ณต๋ถ€ํ•œ ๊ฑธ ์ฃผ์„์œผ๋กœ ๋‹ฌ์•„๋‘”๋‹ค. ๋‚˜์ค‘์— ํ”„๋กœ์ ํŠธ ํ›‘์–ด๋ณผ ๋•Œ ์‰ฌ์šธ ๊ฒƒ ๊ฐ™์•„์„œ ๊ทธ๋ ‡๊ฒŒ ๋†”๋’€๋Š”๋ฐ ๊ทธ๋Ÿฌ๊ธธ ์ž˜ํ•œ ๊ฒƒ ๊ฐ™๋‹ค. ์ „์— ๋งŒ๋“ค์—ˆ๋˜ ํ”„๋กœ์ ํŠธ๋Š” ํšŒ์›๊ฐ€์ž…-๋กœ๊ทธ์ธ(์นด์นด์˜ค๋กœ๊ทธ์ธํฌํ•จ) ๊ธฐ๋Šฅ์„ ๋งŒ๋“  ํ”„๋กœ์ ํŠธ์˜€๋‹ค. ๊ทธ๋•Œ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋•Œ๋ฌธ์— passwordEncoder๋ฅผ ์“ด ๊ฒƒ ๊ฐ™๋‹ค. ์ด ๋ถ€๋ถ„์€ ํ•„์š”์— ๋”ฐ๋ผ ์„ค์ •ํ•˜๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค.

ํ˜„์žฌ ํ”„๋กœ์ ํŠธ์—๋Š” Security๋ฅผ ํฌํ•จ์‹œํ‚ค์ง€ ์•Š์•˜๋‹ค. ๊ธฐ์–ต์— ์˜ํ•˜๋ฉด ๋‚˜์ค‘์— keyclock์„ ์‹œ๋„ํ•ด๋ณด๋ ค๊ณ  ํ˜น์‹œ ๋ชฐ๋ผ์„œ ์•ˆ๋„ฃ์–ด๋‘” ๊ฒƒ ๊ฐ™๋‹ค. ๊ทธ๋ž˜์„œ ์ด ์ฝ”๋“œ๋กœ ์ผ๋‹จ ์˜ฌ๋ฆฌ๊ฒ ๋‹ค.

 

 

 

- WebMvcConfigurer-

 

[Spring Boot] WebMvcConfigurer ์ดํ•ดํ•˜๊ธฐ

WebMvcConfigurer๋Š” Spring MVC์˜ ๊ธฐ๋ณธ ์„ค์ •์„ ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•œ๋‹ค   WebMvcConfigurer ์ดํ•ดํ•˜๊ธฐWebMvcConfigurer๋Š” Spring MVC ์„ค์ •์„ ๊ฐ„ํŽธํ•˜๊ฒŒ ์ปค์Šคํ„ฐ๋งˆ์ด์ฆˆํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ฃผ๋Š” ์ธํ„ฐ

itconquest.tistory.com

 

 

- CORS ์„ค์ •

 

Spring Boot CORS ์„ค์ •ํ•˜๊ธฐ

CORS ๋ž€? Cross-Origin Resource Sharing, CORS ๋‹ค๋ฅธ ์ถœ์ฒ˜์˜ ์ž์›์„ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•˜๋Š”๊ฒƒ์„ ๋งํ•ฉ๋‹ˆ๋‹ค. CORS๋ฅผ ์ œ๋Œ€๋กœ ์„ค์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ์›ํ•˜๋Š” ๋ฆฌ์†Œ์Šค๋ฅผ ๊ณต์œ ๋ฐ›์ง€ ๋ชปํ•œ๋‹ค. CORS ๋ฌธ์ œ๋ฅผ Spring์œผ๋กœ ํ•ด๊ฒฐํ•˜

thewayitwas.tistory.com