일반적으로 브라우저는 보안 문제로 인해 동일 출처 정책(SOP, Same Origin Policy)을 따릅니다. 두 URL의 프로토콜, 호스트, 포트가 모두 같아야 동일한 출처로 볼 수 있는데, 예를 들어 a-service.com 호스트에게 받은 페이지에서 b-service.com 호스트로 데이터를 요청할 수 없습니다. 출처가 다른 호스트로 데이터를 요청하는 경우 CORS 정책을 위반하게 됩니다. 보통 SPA(Single Page Application)은 데이터를 별도 API 서비스에서 받아오기 때문에 Cross Origin 요청이 발생합니다.
CORS 정책 위반 문제를 정석으로 해결하려면 백엔드 서비스 쪽에서 응답 헤더에 필요한 값들을 담아서 전달해야 합니다.
서버로부터 적절한 응답 헤더를 받지 못하면 브라우저에서 에러가 발생합니다. 백엔드 서비스는 정상적인 요청과 응답은 일어나지만, 브라우저에서 에러가 발생한다는 것에 주의해야 합니다
대표적인 SPA인 리액트 어플리케이션에서도 프록시를 이용하면 이를 CORS 정책을 우회할 수 있습니다. 별도의 응답 헤더를 받을 필요 없이 브라우저는 리액트 어플리케이션으로 데이터를 요청하고, 해당 요청을 백엔드 서비스로 전달(pass)합니다. 리액트 어플리케이션이 백엔드 서비스로부터 받은 응답 데이터를 다시 브라우저로 재전달하기 때문에 브라우저는 CORS 정책을 위배한지 모릅니다.
axios 모듈에서 사용한 URI가 상대 경로인지 절대 경로인지 확인합니다.
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;
{
"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"
}