์คํ๋ง๊ณผ ๋ฆฌ์กํธ๋ฅผ ์๋ก ์ฐ๊ฒฐํ ๊ฒ์ด๋ค.
๊ฒ์์ ํ๋ฉด 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 ์ฌ์ฉํ๊ธฐ - Web API | MDN
fetch() ๋ฉ์๋์๋ ์ ํ์ ์ผ๋ก ๋ ๋ฒ์งธ ๋งค๊ฐ๋ณ์๋ ์ ๊ณตํ ์ ์์ต๋๋ค. ์ด ๋งค๊ฐ๋ณ์, init ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ฉด ์ฌ๋ฌ๊ฐ์ง ์ค์ ์ ์ ์ดํ ์ ์์ต๋๋ค. fetch() ๋ฌธ์๋ฅผ ๋ฐฉ๋ฌธํด ์ฌ์ฉ ๊ฐ๋ฅํ ์ ์ฒด ์ต์ ์
developer.mozilla.org
fetch() ์ ์ญ ํจ์ - Web API | MDN
Response ๊ฐ์ฒด๋ก ์ดํํ๋ Promise์ ๋๋ค. ์ ํฌ์ ์ทจ๋ ์์ฒญ ์์ (๋ฏธ๋ฆฌ๋ณด๊ธฐ)์์๋ ์๋ก์ด Request ๊ฐ์ฒด๋ฅผ ์์ฑ์๋ก ์์ฑํ๊ณ , fetch() ํธ์ถ๋ก ์ทจ๋ํฉ๋๋ค. ์ด๋ฏธ์ง๋ฅผ ์ทจ๋ํ๊ณ ์์ผ๋ฏ๋ก, ์์ฒญ์ ์ฌ๋ฐ๋ฅด๊ฒ
developer.mozilla.org
await - JavaScript | MDN
ํ๋ก๋ฏธ์ค ๋๋ thenable ๊ฐ์ฒด์ ์ดํ ๊ฐ์ด๊ฑฐ๋, ํํ์์ด thenable์ด ์๋ ๊ฒฝ์ฐ ํด๋น ํํ์์ ์์ฒด ๊ฐ. ํ๋ก๋ฏธ์ค ๋๋ thenable ๊ฐ์ฒด๊ฐ ๊ฑฐ๋ถ๋๋ฉด, ํด๋น ๊ฑฐ๋ถ ์ฌ์ ๋ฅผ ๋์ง๋๋ค. await๋ ์ผ๋ฐ์ ์ผ๋ก Promise
developer.mozilla.org
- ์ค์ ๋ฐฐํฌ
๊ฐ๋ฐ๋ง ํ ๋๋ ์ด์ ๋๋ง ํด๋ ์ถฉ๋ถํ ๋์์ด ๋๋ค. ๊ทธ๋ฌ๋ ๋ฐฐํฌํ ๋๋ ๊ผญ 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์ ์๋ํด๋ณด๋ ค๊ณ ํน์ ๋ชฐ๋ผ์ ์๋ฃ์ด๋ ๊ฒ ๊ฐ๋ค. ๊ทธ๋์ ์ด ์ฝ๋๋ก ์ผ๋จ ์ฌ๋ฆฌ๊ฒ ๋ค.
[Spring Boot] WebMvcConfigurer ์ดํดํ๊ธฐ
WebMvcConfigurer๋ Spring MVC์ ๊ธฐ๋ณธ ์ค์ ์ ๋ณ๊ฒฝํ๊ฑฐ๋ ์ถ๊ฐํ ์ ์๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํ๋ค WebMvcConfigurer ์ดํดํ๊ธฐWebMvcConfigurer๋ Spring MVC ์ค์ ์ ๊ฐํธํ๊ฒ ์ปค์คํฐ๋ง์ด์ฆํ ์ ์๋๋ก ๋์์ฃผ๋ ์ธํฐ
itconquest.tistory.com
Spring Boot CORS ์ค์ ํ๊ธฐ
CORS ๋? Cross-Origin Resource Sharing, CORS ๋ค๋ฅธ ์ถ์ฒ์ ์์์ ๊ณต์ ํ ์ ์๋๋ก ์ค์ ํ๋๊ฒ์ ๋งํฉ๋๋ค. CORS๋ฅผ ์ ๋๋ก ์ค์ ํ์ง ์์ผ๋ฉด ์ํ๋ ๋ฆฌ์์ค๋ฅผ ๊ณต์ ๋ฐ์ง ๋ชปํ๋ค. CORS ๋ฌธ์ ๋ฅผ Spring์ผ๋ก ํด๊ฒฐํ
thewayitwas.tistory.com