์บก์คํค ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์ ๋ง์ฃผํ๋ ์๋ฌ ์ค ํ๋๋ CORS ์๋ฌ์๋ค. ์ฒ์ ๋ฐฑ์๋ ํ์๋ถ๋ค๊ณผ ์ฐ๋์ ์๋ํ์ ๋ CORS ์๋ฌ๊ฐ ๋ฐ์ํ์ฌ ์กฐ๊ธ ๊ณ ์ํ๋ค ํํ ~ ๐ฅน๐ข
๐CORS(Cross Origin Resource Sharing, ๊ต์ฐจ ์ถ์ฒ ๋ฆฌ์์ค ๊ณต์ )
CORS๋ ๋ค๋ฅธ ์ถ์ฒ(origin) ๊ฐ ๋ฆฌ์์ค๋ฅผ ๊ณต์ ๊ฐ๋ฅํ๋๋ก ํ๋ ๋งค์ปค๋์ฆ์ด๋ค. CORS๋ ๋ค๋ฅธ ์ถ์ฒ์ ์์์ ๊ณต์ ๋ฅผ ๊ฐ๋ฅํ๊ฒ ๋ง๋ค๋ฉฐ, ํ ์ถ์ฒ์์ ์คํ ์ค์ธ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ด ๋ค๋ฅธ ์ถ์ฒ์ ์ ํํ ์์์ ์ ๊ทผํ ์ ์๋ ๊ถํ์ ๋ถ์ฌํ๋๋ก ๋ธ๋ผ์ฐ์ ์ ์๋ ค์ค๋ค. ์ด๋, CORS์๋ฌ๋ ๋ธ๋ผ์ฐ์ ๊ฐ ๋ณด์ ์ ์ฑ ์ค ํ๋์ธ SOP ์ ์ฑ ์ ๋ฐ๋ฅด๊ธฐ ๋๋ฌธ์ ๋ฐ์ํ๋ค.
๐SOP(Same Origin Policy, ๋์ผ ์ถ์ฒ ์ ์ฑ )
๋ธ๋ผ์ฐ์ ๋ ๋ณด์ ์ ์ฑ ์ค ํ๋์ธ SOP ์ ์ฑ ์ ๋ฐ๋ฅธ๋ค. SOP๋ ๋์ผํ ์ถ์ฒ(Origin) ์ฌ์ด์์๋ง ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์ ์ ์๋๋ก ํ๋ ์ ์ฑ ์ด๋ค. ์ด๋, ๋์ผํ ์ถ์ฒ์ ๊ธฐ์ค์ ํ๋กํ ์ฝ, ๋๋ฉ์ธ, ํฌํธ์ด๋ค.
์๋ฅผ ๋ค์ด, http://localhost:8000๊ณผ http://localhost:8000/posts๋ ๊ฐ์ ์ถ์ฒ์ด๊ธฐ ๋๋ฌธ์ ์ํธ์์ฉ์ด ๊ฐ๋ฅํ๋ฐ, http://stonesy.com์์ http://localhost:8000์ ํธ์ถํ๋ ๊ฒ์ SOP์ ์๋ฐฐ๋๋ค.
๋ธ๋ผ์ฐ์ ๊ฐ SOP ์ ์ฑ ์ ๋ฐ๋ฅด๋ ์ด์ ๋ XSS, CSRF ๊ณต๊ฒฉ์ ๋ฐฉ์งํ๊ธฐ ์ํด์์ด๋ค. XSS(Cross-Site Scripting) ๊ณต๊ฒฉ์ด๋ ๊ณต๊ฒฉ์๊ฐ ์น ์ฌ์ดํธ์ ์ ์์ ์ธ ์คํฌ๋ฆฝํธ๋ฅผ ์ฝ์ ํด์ ์ค์ํ ์ ๋ณด๋ฅผ ํ์ทจํ๊ฑฐ๋ ๋ค๋ฅธ ์น ์ฌ์ดํธ๋ก ๋ฆฌ๋ค์ด๋ ์ ์ํค๋ ๊ณต๊ฒฉ์ด๋ค. CSRF(Cross-Site Request Foregery, ์ฌ์ดํธ๊ฐ ์์ฒญ ์์กฐ ๊ณต๊ฒฉ) ๊ณต๊ฒฉ์ด๋ ์ธ์ฆ๋ ์ฌ์ฉ์๊ฐ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ํน์ ์์ฒญ์ ๋ณด๋ด๋๋ก ์ ๋ํ๋ ๊ณต๊ฒฉ์ด๋ค.
๋ก์ปฌ์์ ๊ฐ๋ฐ ์ค์ธ React(http://localhost:3000)์์ Spring(http://localhost:8080)์ผ๋ก ์์ฒญ์ ๋ณด๋์ ๋ ๋ค์๊ณผ ๊ฐ์ด cors ์๋ฌ๊ฐ ๋ฐ์ํ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
๊ทธ๋ ๋ค๋ฉด CORS ์๋ฌ๋ ์ด๋ป๊ฒ ํด๊ฒฐํด์ผํ ๊น?
๐CORS ์๋ฌ์ ํด๊ฒฐ๋ฐฉ๋ฒ
1.access-control-allow-origin ํค๋
๊ธฐ๋ณธ์ ์ผ๋ก ๋ฐฑ์๋์์ access-control-allow-origin ํค๋๋ฅผ ํตํด SOP ์ ์ฑ ์ ์ํํ ์ ์๋ค. ์ด๋ ๊ฐ์ฅ ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ๋๋ ๋ฐฉ๋ฒ์ผ๋ก, access-control-allow-origin ํค๋๋ฅผ ํตํด ํน์ ์ถ์ฒ๋ ๋ชจ๋ ์ถ์ฒ์์์ ์ ๊ทผ์ ํ์ฉํ ์ ์๋ค.
2. ํ๋ก์ ์๋ฒ
ํ๋ก ํธ์๋์์ ํ๋ก์ ์๋ฒ๋ฅผ ๋์ด CORS ์๋ฌ๋ฅผ ์ฐํํ ์ ์๋ค. ์ด๋ ๊ฐ๋ฐ ๋จ๊ณ์์ ์ ์ฉํ๋ฉฐ, ์ค์ ์ด์ ๋จ๊ณ์์๋ ์ด๋ฅผ ์ ๊ฑฐํ๊ฑฐ๋ ์์ ํด์ผ ํ๋ค.
3. Fetch API mode ์ต์
- cors: ๋ชจ๋ ์์ฒญ์ด CORS ์ ์ฑ ์ ์ค์ํ๋๋ก ํ๋ค. ์๋ฒ๊ฐ ์ฌ๋ฐ๋ฅธ CORS ํค๋(access-control-allow-origin)์ ๋ฐํํด์ผ ์์ฒญ์ด ์ฑ๊ณตํ๋ค.
- no-cors: ๋ชจ๋ ์์ฒญ์ด CORS ์ ์ฑ ์ ๋ฌด์ํ๋ค๋ก ํ๋ค. ์ด ๋ชจ๋๋ ๋ค๋ฅธ ์ถ์ฒ๊ฐ ๋ฆฌ์์ค ๊ตํ์์๋ ๋ธ๋ผ์ฐ์ ๊ฐ CORS ์๋ฌ๋ฅผ ๋ฐ์์ํค์ง ์๊ณ ์ฒ๋ฆฌํ์ง๋ง ์๋ต์ ์ ๊ทผํ๋ ๊ฒ์ ์ ํ๋ ์ ์๋ค.
- same-origin: ์์ฒญ์ด ๋์ผ ์ถ์ฒ์์๋ง ์ ํ๋๋๋ก ํ๋ค.
๐React์ ์ ์ฉ
1. http-proxy-middleware ์ฌ์ฉํ๊ธฐ
๋จผ์ http-proxy-middleware๋ฅผ ์ค์นํด์ผ ํ๋ค. npm์ด๋ yarn์ ์ด์ฉํด http-proxy-middleware๋ฅผ ์ค์นํ๋ค.
npm install http-proxy-middleware
yarn add http-proxy-middleware
./src ํด๋์ setupProxy.js ํ์ผ ์์ฑํํ๋ค. ์ค์ํ ์ ์ typescript๋ก ๊ฐ๋ฐํ๊ณ ์๋๋ผ๋ setupProxyํ์ผ์ js๋ก ์์ฑํด์ผ ํ๋ค. 2021๋ 7์ 14์ผ ๊ธฐ์ค์ผ๋ก Typescript template CRA๋ http-proxy-middleware๋ฅผ ์ง์ํ์ง ์๋๋ค๊ณ ํ๋ค.(์ฐธ๊ณ : https://egas.tistory.com/39) ํ์ฌ๋ ์ด๋ค์ง ๋ชจ๋ฅด๊ฒ ๋๋ฐ, ์ค์ ๋ก ํ ์คํธํด๋ณด์์ ๋ setupProxy๋ฅผ ts๋ก ์์ฑํ๋๊น ์๋ฌ๊ฐ ํด๊ฒฐ๋์ง ์์๋ค.
// src/setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/api',
createProxyMiddleware({
target: 'http://localhost:8080',
changeOrigin: true,
})
);
};
target์๋ api ์์ฒญ ์ฃผ์๋ฅผ ์์ฑํ๋ค. changeOrigin์ ํตํด ํธ์คํธ ํค๋๊ฐ ๋ณ๊ฒฝ๋๋ค. ์ ์ฝ๋์ ์๋ฏธ๋ "http://localhost:3000/api"๋ก ์์๋๋ ์์ฒญ์ "http://localhost:8080/api"๋ก ํ๋ก์ฑ๋๊ฒ ํ๋ผ๋ ์๋ฏธ์ด๋ค.
import React, { useEffect } from 'react';
import axios from 'axios';
function App() {
useEffect(() => {
axios.get('/api/hello')
.then(res => {
if (res.status === 200) {
console.log(res.data);
}
})
.catch(error => {
console.log(error);
});
});
return (
<div>
Hello
</div>
);
}
export default App;
์ฐธ๊ณ ๋ก axios๋ก ์์ฒญ์ ๋ณด๋ผ ๋๋ "http://localhost:8080~"๋ฅผ ๋ชจ๋ ์์ฑํ๋ ๊ฒ์ด ์๋ ์๋ํฌ์ธํธ๋ง ์์ฑํด์ผ ํ๋ค.
ํด๊ฒฐ!
2. package.json ์์ ํ๊ธฐ
3. spring์ ์ค์ ํ์ผ ์ถ๊ฐํ๊ธฐ
react์ธก์์ ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ์ ์๋์ง๋ง ๋ฐฑ์๋์์ ๋ค์ ํ์ผ์ ์ถ๊ฐํ์ฌ ํด๊ฒฐํ ์ ์๋ค. ์ฐ๋ฆฌ๋ ์ค์ ๋ก ์ด ๋ฐฉ๋ฒ์ ํตํด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์๋ค ใ ใ ~
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 {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
}
}
ํ์ง๋ง ์ ๋ฐฉ๋ฒ๋ค์ ๊ฐ๋ฐ ๋จ๊ณ์์๋ง ์ฌ์ฉ ๊ฐ๋ฅํ๋ค.
์๋ฅผ ๋ค์ด, netfliy๋ฅผ ํตํด ๋ฐฐํฌํ๋ค๊ณ ํ์ ๋ https://docs.netlify.com/routing/redirects/rewrites-proxies/#proxy-to-another-service๋ฅผ ์ฐธ๊ณ ํ๋ฉด..
์ต์์ ํด๋์ netlify.toml์ด๋ผ๋ ํ์ผ์ ์์ฑํ๊ณ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ๋ค.
[[redirects]]
from = "/api/*"
to = "http://localhost:8080/:splat"
status = 200
์ด๋, :splat์ ์๋ฏธ๋ *๊ณผ ๋์ผํ๋ค.
์ด์จ๋ ์ค์ํ ์ ์ ์์ ์ค๋ช ํ ๋ฐฉ๋ฒ๋ค์ ๋ก์ปฌ ํ๊ฒฝ์์ ๊ฐ๋ฐํ ๋๋ง ์ ์ฉ๋๋ ๋ฐฉ๋ฒ์ธ ๊ฒ ๊ฐ๋ค.
๐์ถ์ฒ