๐Ÿ’ป์›น(Web)/React

[React]CORS ์—๋Ÿฌ

stonesy 2023. 6. 16. 23:22
728x90

์บก์Šคํ†ค ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ ๋งˆ์ฃผํ–ˆ๋˜ ์—๋Ÿฌ ์ค‘ ํ•˜๋‚˜๋Š” 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์˜ ์˜๋ฏธ๋Š” *๊ณผ ๋™์ผํ•˜๋‹ค.

์–ด์จŒ๋“  ์ค‘์š”ํ•œ ์ ์€ ์œ„์— ์„ค๋ช…ํ•œ ๋ฐฉ๋ฒ•๋“ค์€ ๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ ๊ฐœ๋ฐœํ•  ๋•Œ๋งŒ ์ ์šฉ๋˜๋Š” ๋ฐฉ๋ฒ•์ธ ๊ฒƒ ๊ฐ™๋‹ค.

 

 

๐Ÿ”Ž์ถœ์ฒ˜

https://www.datoybi.com/http-proxy-middleware/#:~:text=CORS%20Error%EB%8A%94%20Server%EC%97%90%EC%84%9C,%EB%A5%BC%20%EC%84%B8%ED%8C%85%ED%95%B4%EC%A3%BC%EB%A9%B4%20%ED%95%B4%EA%B2%B0%EB%90%A9%EB%8B%88%EB%8B%A4.

 

 

728x90