본문 바로가기

카테고리 없음

CORS 실습해보기 (백엔드 방식과 vite 개발환경의 방식)

CORS 오류는 다른 출처의 리소스를 요청할 때 발생할 수 있다.

자신의 출처(도메인, 프로토콜, 포트)와 다른 서버에 요청을 보낼 때 CORS 오류를 발생시키지 않으려면 별도의 세팅을 해야한다.

정석적으로는 백엔드에서 처리하지만 백엔드와 소통이 안되는 경우 혹은 공공 API를 사용하는 경우에 프록시 서버를 통해 클라이언트에서도 처리할 수 있다.

⭐ 원리 설명

1. 웹 브라우저가 서버에 리소스를 요청할 경우 자동적으로 요청 헤더의 Origin에 출처를 담아서 보낸다.
2. 서버에서는 모든 요청에 대해 환영하고 응답을 보내는 데 응답 헤더의 Access-Control-Allow-Origin에 허용할 출처를 담아서 응답한다.
3. 웹 브라우저가 응답을 받기 전에 요청 헤더의 Origin과 응답 헤더의 Access-Control-Allow-Origin를 비교하여,

  • 일치 => 응답을 수용한다.
  • 불일치 => 응답을 버린다. 그리고 cors 오류를 발생시킴

CORS 오류 예시

프론트엔드 주소는 http://localhost:5173/ 이다.

카테고리를 조회하는 API 주소는 http://localhost:8000/category 이다.

아래는 postman으로 카테고리 조회를 성공한 결과이다. 즉 로직 상의 오류는 없다.

하지만 웹 브라우저(프론트엔드)에서 API를 요청하면 cors 오류가 발생한다. 개발자 도구의 네트워크 탭에서 Origin과 Access-Control-Allow-Origin를 확인할 수 있다.

cors 오류가 발생
요청 헤더의 Origin

요청 헤더의 Origin은 http://localhost:5173이다. 하지만 응답 헤더의 Access-Control-Allow-Origin은 없었고, 이런 경우도 불일치로 판단하여 cors 오류를 발생시켰던 것이였다.

해결하는 방법

1. 백엔드에서 해결

Access-Control-Allow-Origin을 웹 브라우저와 같은 출처(Origin)로 설정해주면 된다.

Node.js의 express는 cors라는 미들웨어를 제공하고 있으므로 이를 활용하였다.

cors 미들웨어

Origin과 Access-Control-Allow-Origin가 일치하여 api가 성공적으로 요청받은 것을 확인할 수 있다.

요청 헤더의 Origin과 응답 헤더의 Access-Control-Allow-Origin

2. 프론트엔드에서 해결

- vite.config.js

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  server: {
    // Proxy 설정
    proxy: {
      // api로 시작하는 모든 api
      "/api": {
        // 서버 base 주소
        target: "http://localhost:8000",
        // target으로 변경
        changeOrigin: true,
        // 요청 경로에서 '/api' 제거
        rewrite: (path) => path.replace(/^\/api/, ""),
      },
    },
  },
});

 

설명

'/api'로 시작하는 모든 api에 프록시 요청을 보내도록 설정해주었다.

따라서 서버 api가 http://localhost:8000/category인 경우, 나중에 요청할 때 /api/category로 요청해야한다

 

- Category.jsx

api를 날리는 컴포넌트

모든 api의 endpoint 시작부분에 /api를 붙여야한다.

import axios from "axios";
import { useEffect, useState } from "react";
const Cors = () => {
  const [categorys, setCategorys] = useState([]);
  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await axios.get("/api/category");
        console.log(1);
        setCategorys(response.data);
      } catch (err) {
        console.log(err);
      }
    };

    fetchData();
  }, []);

  return (
    <div>
      <ul>
        {categorys.map((category) => (
          <li key={category.category_id}>{category.category_name}</li>
        ))}
      </ul>
    </div>
  );
};

export default Cors;

 

proxy로 요청하면 응답 헤더의 Access-Control-Allow-Origin: * 이 되므로, 성공

응답 헤더 *

쿠키 전달 : credentials

⭐ 만약 자격 증명 API(ex. 쿠키 전달) 경우에는 Access-Control-Allow-Origin가 * 가 아니라 정확한 출처를 기입해야한다. 또한 credentials: true 로 옵션을 주어야 한다.

 

-  express의 cors 미들웨어

express

이 때, origin : * 이거나 credentials : false(기본값) 이면 CORS 오류가 발생한다.

 

- react

프론트엔드에서도 withCredentials : true 로 옵션을 주어야 한다.

import axios from "axios";

const Cors = () => {
  const email = "qqqqq@email.com";
  const password = "qwer";
  const fetchData = async () => {
    try {
      await axios.post(
        "http://localhost:8000/users/login",
        {
          email,
          password,
        },
        {
          withCredentials: true, // 설정
        }
      );
    } catch (err) {
      console.log(err);
    }
  };

  const handleLogin = () => {
    fetchData();
  };
  return (
    <div>
      <button onClick={() => handleLogin()}>Login</button>
    </div>
  );
};

export default Cors;

 

withCredentials : false 라도 오류는 발생하지 않지만, 쿠키를 받을 수가 없다. 

 

추가적으로 만약 프론트에서 proxy 서버를 사용한다면 cors의 origin 뿐만 아니라 `credentials: true` 와 같은 옵션을 백엔드에 설정하지 않아도 될 수 있다. 이는 프록시 서버의 설정에 따라 상이하다.

이전 코드에서의 vite의 proxy는 가능한 것을 확인하였음!