[React] CORS from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. 오류해결중

2023. 8. 6. 21:44_Web/React

728x90

문제상황

Access to XMLHttpRequest at 'http://localhost:8080/booking/' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.


정리

 

0. 응답 패킷에 Access-Control-Allow-Origin 이 있는지 확인하기

1. package.json이나 setupProxy.js가 작동해서 3000포트가 아닌 것을 네트워크-요청URL에서 확인하기 => domain이 같아서 localhost:3000에서 데이터를 가져올 수 있는 경우가 있다. 이러면 백엔드에서 데이터를 가져오지 못하므로 수정해야한다.

2. 서버에 해당하는 ex)/api로 호출하는 방법인지 확인하기

3. 리액트 캐시가 남아있는지 확인하고 npm install 다시 설치하기

4. 서버에 cors로 프론트 허용하기 (아래 코드 참고)

 

 

 

아래 영상이 CORS(콜스라고 읽는다)가 왜 필요한지에 대해 자세히 정리해놨다.

https://www.youtube.com/watch?v=bW31xiNB8Nc 

 

짧게 정리하자면 주소가 다른 웹사이트에서 정보를 요청할때 막는 것을 SOP라고 하고 이를 허용하는 것이 CORS라고 한다. 이는 '프론트에서' 발생하지만 벡엔드에서 해결해야 한다. 이 문제는 포스트맨에서도 발견되지 않고, 크롬과 같은 브라우저에서 발생하는 보안정책이다. 토큰, 세션, 캐시와 같은 곳에서 발생하는 인증정보 탈취문제를 예방하기 위해서 사용하기에 따라서 백엔드에서 프론트를 CORS(Cross-Origin Resource Sharing) 허용해줘야한다.

 

- * 와일드카드 사용시 누구나 사용가능

- CORS를 허용해주는 주소를 허용해야함

- Origin 이라는 header를 추가해야함 이게 서버의 답장 헤더에 담긴 Access-Control-Allow-Origin에 있으면 허용

 

아래처럼 addCorsMappings를 사용하면 스프링부트에서 허용해준다.

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableWebMvc
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedMethods("GET", "POST", "PUT", "DELETE")
            .allowedOrigins("*");
    }
}

 

리액트 코드

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function App() {
  const [data, setData] = useState([]);

  useEffect(() => {
    // Replace with your Spring Boot API URL
    const apiUrl = 'https://your-spring-boot-api.com/api';

    axios.get(apiUrl)
      .then(response => {
        setData(response.data);
      })
      .catch(error => {
        console.error('Error fetching data:', error);
      });
  }, []);

  return (
    <div>
      {data}
    </div>
  );
}

export default App;

 

 

GET 302가 뜨면 cors는 일단 해결한 셈이다. 


문제 해결 과정

 

카카오 로그인을 이용한 REST API를 사용하는 도중 해당 오류가 발생했다.

 

구글링 끝에 백엔드 쪽에서 Access-Control-Allow-Origin 헤더가 없어서 header 설정을 했고 프론트 쪽에서는 proxy 설정을 했다 된줄 알았다. 아래 사진을 보면 개발자도구-네트워크-패킷의 헤더를 볼 수 있다.

 

 

 

<백엔드 스프링>

 

<프론트 리액트>

package.json 에 추가

  "proxy": "http://localhost:8080/",

 

 

 


아래는 사용한 코드이다.

const apiRoot = axios.create({
  baseURL: "http://localhost:8080/",
});
 
 const [check, setCheck] = useState("");
  useEffect(() => {
    fetchDataCheck();
    // fetchData();
  }, []);

  const fetchDataCheck = async () => {
    try {
      const response = await apiRoot.get("/booking/api/hello", {
        withCredentials: true,
      });
      if (!response.data) {
        throw new Error("Failed to fetch data from the server");
      }
      setCheck(response.data);
      console.log(response.data);
    } catch (error) {
      console.error(error);
    }
  };
 
 return (
    <div>
      <h2>Booking Data:</h2>
      {check.length > 0 ? (
        <ul>
          <h1>{check}도착</h1>
        </ul>
      ) : (
        <p>Loading...</p>
      )}
    </div>
  );

으로 서버가 연결된 것을 확인했다.

 

그러나 서버에서 Booking과 같이 값을 가져오는데 문제가 생겼는데

const URL = "/booking";
     
const response = await axios.get(URL, {
        withCredentials: true,
      });

윗 코드를 사용해서 해결했다.

 

문제가 되는 코드는 아래 코드이다

const apiRoot = axios.create({
  baseURL: "http://localhost:8080/",
});
 

  const fetchData = async () => {
    console.log("hi1");
    try {
      // const response = await axios.get(URL, {
      //   withCredentials: true,
      // });
      const response = await apiRoot.get("/booking/", {
        withCredentials: true,
      });
      console.log(response.data);
      console.log("hi2");
      if (!response.data) {
        throw new Error("Failed to fetch data from the server");
      }
      console.log(response.data);
      setBookingData(response.data);
      // console.log(bookingData);
    } catch (error) {
      console.log("hi");
      console.error(error);
    }
  };

 

왜 apiRoot를 만들어서 axios.create를 사용하면 CORS가 발생하는 걸까? 무슨 차이점이 있던걸까?

 

개발자도구 - 네트워크를 통해서 헤더를 뜯어봤다. 문제가 발생하는 경우는 헤더가 없다. 아무래도 apiRoot는 create로 URL을 생성하였기 때문에 헤더를 달아줘야한다.

 

그러나 proxy를 통한 axios.get(URL,);은 헤더가 포함되어 있다.

 

둘의 차이를 보자면 요청 URL에 차이가 있다. 3000/booking에 요청하면 값을 받을 수 있지만, 8080/booking에 요청하면 값을 받을 수 없다. 3000?

 

 

따라서 apiRoot를 3000으로 수정하면 URL을 불러올 수 있다. 궁금점이 생겨서 proxy를 해제하고 8080의 apiRoot를 적용해보기로했다. 여기서부터 뭔가 이상함을 느낄 수 있었다.

 

  "proxy": "http://localhost:8080/",

프론트 입장에선 proxy가 있든 말든 요청 URL만 잘 맞추면 되는 거였다. CORS 오류가 스프링 8080서버와 리액트 3000 포트번호가 달라서 발생하는 문제인데 이게 된다고..? 껐다 키니까 안 된다. 다시 proxy 설정을 하니 적용이 되었다. proxy가 된 줄 알았다

 

요청 URL 주소 잘 확인하자.

 

그런데 이게 왜 되는건지? 싶은데 아래 블로그가 상황이 똑같다. 3000에 요청해도 되네요?? 왜요??

https://study-easy-coding.tistory.com/146

 

이는 SOP (Same-Origin Policy)라고 부르며, 프런트엔드와 백엔드가 같은 도메인이에서 실행되는 경우에만 적용된다.고 한다... 따라서 8080으로 요청을 받아야 한다. 아직도 CORS가 막혀있는거다.

 

따라서 

npm install http-proxy-middleware --save

로 proxy 라이브러리를 설치해서 setProxy.js 를 작성하기로했다. -> setupProxy.js 로 작성해야한다.

 

// src/main/frontend/src/setProxy.js

const { createProxyMiddleware } = require("http-proxy-middleware");

module.exports = function (app) {
  app.use(
    "/api",
    createProxyMiddleware({
      target: "http://localhost:8080", // 서버 URL or localhost:설정한포트번호
      changeOrigin: true,
    })
  );
};

위를 사용하려면  /api/hello 로 불러와야한다. 경로에 /api가 있다면 8080으로 호출하는 코드이다.

 

생각해보니 벡엔드 쪽은 /api/hello로 구현해두어서 연결이 성공했던건가? -> 알고보니 백엔드에서 해당 코드만 cors를 허용해주었다.

 

상대경로와 절대경로에 대해서 찾아봤다

 

이것도 해당이 안되었다. 아무래도 Proxy 자체가 작동하지 않는다.

아래 블로그를 참고해서 코드를 입력해서 cache랑 모두 삭제하고 다시 설치했다.

# rm -r package-lock.json
# rm -r node_modules
# npm install

 

npm install 오류는 

npm install react-paypal-express-checkout --save --legacy-peer-deps

으로 해결했다.


https://velog.io/@eunnbi/Project-cra-proxy-setting 

 

그런데 다시 깔아봐도 똑같았기에 아예 새 창을 만들어서 프록시가 작동하는지 부터 확인했다. 설마설마 했는데 setupProxy.js 였다. 

그래도 http://localhost:3000/api 로 호출하길래 찾아봤다.

https://velog.io/@tw4204/React-%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C%EC%9D%98-CORS%EB%A5%BC-%EC%9C%84%ED%95%9C-proxy-%EC%84%A4%EC%A0%95

 

위 블로그는 ts를 사용해서,,, 좀 안맞은거 같지만 ws option을 사용하는 걸 보고, 아래 블로그로 넘어갔다.

https://maeryo88.tistory.com/339

 

멀티프록시 사용

yarn add http-proxy-middleware

도 안된다........ 도대체 뭐지

 

공식문서

https://www.npmjs.com/package/http-proxy-middleware

"http-proxy-middleware": "^2.0.6",

 

프록시 관련 새로운 문제 발생

[HPM] Error occurred while proxying request localhost:3000/local to http://localhost:4000/ [ECONNREFUSED] (https://nodejs

 

npm install concurrently --save

터미널 분리를 위해 이건 서버를 다시 키면 오류가 수정된다고 한다. 아무래도 터미널 분리 문제이다.

이건 4000에 만들어둔 서버가 없기에 발생하는거 같다는 무슨 바로 8080 proxy 오류가 발생했다.

 

 

setupProxy.js를 없애보니

아 백엔드 서버를 안 열고 켜서 그런거였다. 머쓱,, 은 무슨 그대로다

/node_modules/react-scripts/config/webpackDevServer.config.js 에서 

바꿔 봤지만 그대로다.

 

 

이 블로그에선 해당 문제는 서버에서 발생한다고 해서 서버로 넘어갔다. npm run dev 명령어가 뭐지?

https://velog.io/@aksel26/HPM-Error-occurred-while-trying-to-proxy-request

 

[HPM] Error occurred while trying to proxy request

\[HPM] Error occurred while trying to proxy request /api/users/auth from localhost:3000 to http://localhost:5000 (ECONNREFUSED) (https://nod

velog.io

아무래도 npm run 을 하는 방법인거 같다. script를 통해서 할 수 있다.

 

https://godnr149.tistory.com/169

 

npm start : error

에러메세지 [HPM] Error occurred while trying to proxy request cms/admin/home from localhost:3000 to http://localhost:4200/ (ECONNREFUSED) (https://nodejs.org/api/errors.html#errors_common_system_errors) [HPM] Error occurred while trying to proxy reque

godnr149.tistory.com

npm run build -> 서버 실행 -> 리액트 실행하니까 없어졌지만 proxy는 설정되지 않았다.

 

 

https://www.youtube.com/watch?v=N4yUiQiTvwU 

 

yarn add cors 사용하기

yarn add cors

 

 

 

해결중 ....

 

 

아래 블로그 참고

https://snowdeer.github.io/openshift/2020/06/13/react-for-cors-using-proxy/