proxy

김용희·2023년 10월 26일
0

1. CORS(Cross Origin Resource Sharing)

일반적으로 브라우저는 보안 문제로 인해 동일 출처 정책(SOP, Same Origin Policy)을 따릅니다. 두 URL의 프로토콜, 호스트, 포트가 모두 같아야 동일한 출처로 볼 수 있는데, 예를 들어 a-service.com 호스트에게 받은 페이지에서 b-service.com 호스트로 데이터를 요청할 수 없습니다. 출처가 다른 호스트로 데이터를 요청하는 경우 CORS 정책을 위반하게 됩니다. 보통 SPA(Single Page Application)은 데이터를 별도 API 서비스에서 받아오기 때문에 Cross Origin 요청이 발생합니다.

2. 리액트(React) 어플리케이션 프록시(proxy) 구축하기

CORS 정책 위반 문제를 정석으로 해결하려면 백엔드 서비스 쪽에서 응답 헤더에 필요한 값들을 담아서 전달해야 합니다.

서버로부터 적절한 응답 헤더를 받지 못하면 브라우저에서 에러가 발생합니다. 백엔드 서비스는 정상적인 요청과 응답은 일어나지만, 브라우저에서 에러가 발생한다는 것에 주의해야 합니다
대표적인 SPA인 리액트 어플리케이션에서도 프록시를 이용하면 이를 CORS 정책을 우회할 수 있습니다. 별도의 응답 헤더를 받을 필요 없이 브라우저는 리액트 어플리케이션으로 데이터를 요청하고, 해당 요청을 백엔드 서비스로 전달(pass)합니다. 리액트 어플리케이션이 백엔드 서비스로부터 받은 응답 데이터를 다시 브라우저로 재전달하기 때문에 브라우저는 CORS 정책을 위배한지 모릅니다.

  • 리액트 어플리케이션으로부터 화면을 전달받습니다. 이때 호스트는 http://localhost:3000입니다.
  • 화면 버튼을 눌렀을 때 브라우저가 백엔드 서비스(http://localhost:8080)로 직접 요청합니다.
  • 백엔드 서비스는 요청에 대한 응답을 반환합니다.
  • 응답 헤더 정보에 Access-Control-Allow-Origin: http://localhost:3000가 추가됩니다.
  • 이는 백엔드 서비스가 http://localhost:3000 출처로부터 오는 요청은 허가한다는 의미입니다.

3. 테스트코드

axios 모듈에서 사용한 URI가 상대 경로인지 절대 경로인지 확인합니다.

  • non cors header 버튼 - localhost:8080 서버로 직접 요청하고, CORS 관련 응답 헤더 정보를 받지 못합니다.
  • cors header 버튼 - localhost:8080 서버로 직접 요청하고, CORS 관련 응답 헤더 정보를 받습니다.
  • nonProxy 버튼 - localhost:3000 리액트 어플리케이션으로 요청하고, 경로에 따른 프록시 설정을 하지 않습니다.
  • proxy 버튼 - localhost:3000 리액트 어플리케이션으로 요청하고, 경로에 따른 프록시 설정을 수행합니다.
import './App.css';
import {useState} from "react";
import axios from "axios";

function App() {

    const [message, setMessage] = useState('');

    const responseHandler = ({data}) => {
        setMessage(data);
        return data;
    };

    const errorHandler = ({message}) => {
        setMessage(message);
        return message;
    };

    const onNonCorsHeaderHandler = () => {
        axios.get('http://localhost:8080/not-cors')
            .then(responseHandler)
            .catch(errorHandler);
    };

    const onCorsHeaderHandler = () => {
        axios.get('http://localhost:8080/cors').then(responseHandler);
    };

    const onNonProxyHandler = () => {
        axios.get('/not-proxy')
            .then(responseHandler)
            .catch(errorHandler);
    };

    const onProxyHandler = () => {
        axios.get('/proxy').then(responseHandler);
    };

    return (
        <div className="App">
            <p>
                {message}
            </p>
            <div>
                <button onClick={onNonCorsHeaderHandler}>non cors header</button>
                <button onClick={onCorsHeaderHandler}>cors header</button>
                <button onClick={onNonProxyHandler}>nonProxy</button>
                <button onClick={onProxyHandler}>proxy</button>
            </div>
        </div>
    );
}

export default App;

4. package.json 파일을 이용한 프록시 설정

{
  "name": "front-end",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.16.1",
    "@testing-library/react": "^12.1.2",
    "@testing-library/user-event": "^13.5.0",
    "axios": "^0.25.0",
    "http-proxy-middleware": "^2.0.1",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-scripts": "5.0.0",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "proxy": "http://localhost:8080"
}

테스트 결과

  • non cors header 버튼
    localhost:8080 서버로 직접 요청합니다.
    CORS 정책 위반에 대한 에러 메세지가 출력됩니다.
    axios 모듈의 catch 부분에서 에러 메세지를 화면에 출력합니다.
  • cors header 버튼
    localhost:8080 서버로 직접 요청합니다.
    서버로부터 전달받은 데이터를 정상적으로 화면에 출력합니다.
  • nonProxy 버튼
    localhost:3000 리액트 어플리케이션으로 요청합니다.
    특정 경로에 해당되는 프록시 설정을 하지 않았지만, 백엔드 서비스로 요청이 전달됩니다.
    서버로부터 전달받은 데이터를 정상적으로 화면에 출력합니다.
  • proxy 버튼
    localhost:3000 리액트 어플리케이션으로 요청합니다.
    특정 경로에 해당되는 프록시 설정을 하지 않았지만, 백엔드 서비스로 요청이 전달됩니다.
    서버로부터 전달받은 데이터를 정상적으로 화면에 출력합니다.
profile
He threw his knapsack over the brick wall

0개의 댓글