안녕하세요. 김용성입니다.
오늘 면접을 볼 일이 있었는데 생각보다 잘 보지 못한 것 같아요.
너무 긴장을 한 탓인지, 공부해놓았다고 생각했던 부분도 놓치고, 질문에 대해 조금씩 나사빠진 대답을 늘어놓았던 것 같다는 생각이 들더군요. 아직 제가 부족하다는 것도 깨달았습니다.
제대로 된 대답을 하지 못했던 것이 cross-origin 이슈에 대해 설명하는 것이었는데요.
2월 달에 CORS 관련한 이슈를 맞이했던 경험이 있었음에도 정리를 해놓지 않아 오늘 대답을 못했다는 생각이 들어 내내 아쉬운 마음이 들었습니다. 이번 기회에 이 주제에 대해 정리해놓아야겠다는 생각이 들어 오늘의 주제는 CORS로 선정하게 되었습니다.
CORS는 바로 크로스 오리진 리소스 쉐어링(CROSS-ORIGIN-RESOURCE-SHARING)을 뜻합니다. 말 그대로 CORS는 웹 브라우저에서 도메인이 다른 서버끼리 서로 요청을 주고 받을 수 있게끔 해주는 것인데요.
웹 브라우저의 기본 정책은 본래 도메인이 다르면 요청을 주고 받을 수 없게 하는 것이었습니다.
하지만 여러 과정을 거치며 크로스 오리진 리소스 쉐어링, 즉 CORS란 방식을 통하면 그게 가능하게끔 해주었습니다.
요즘에는 프론트엔드 레이어와 API 서버 레이어를 따로 구성하는 경우가 많죠? API 백엔드 서버를 구축하고, 여러 SPA 프레임워크를 사용하여 클라이언트를 구축하여 둘 간의 통신을 하도록 하는 경우가 많을겁니다.
저 또한 그런 작업을 많이 진행하고요. 즉 웹 프론트엔드 사이트 따로, 서버 따로 둔다는 것이죠.
이런 경우에 보통 웹 프론트엔드에서 다른 도메인 위치한 API 서버로 요청을 넣어야 하는 상황이 생깁니다.
아마 이것이 지원되지 않는 경우는 보신 분이 드물 것이라 생각해요.
그렇기에 이런 기능은 당연히 지원되어야 하는거 아닌가? 라고 생각하실 수도 있지만 예전에는 이게 당연한게 아니었습니다.
어떤 과정을 통해 지금의 CORS 방식이 가능해졌는지 살펴보도록 하겠습니다.
예전에 웹사이트를 만든다 하면 상당수가 위 그림과 같은 구조였습니다. 유저가 웹 브라우저 주소창에 주소값을 입력을 하면은 해당하는 서버로 요청을 보내게 되겠죠.
그러면 서버에서는 응답을 할 때 HTML 페이지를 반환합니다. 하나의 서버에서 비즈니스 로직과 HTML 페이지 빌드를 같이 하는게 일반적이었습니다.
이러한 작업은 당연하게도 같은 도메인에서 일어났다는 점을 기억해주세요!
심지어 중간에 웹사이트에서 서버로 자바스크립트를 이용해서 추가 요청을 넣는다고 해도 이건 어디까지나 같은 도메인에서 일어나는 일이었습니다.
그 당시에는 이런 웹사이트에서 다른 서버로 요청을 날린다 하면 '그럴 수 있겠구나' 이런게 아니라 뭔가 개인정보 유출, 피싱 사이트와 같이 보안상 악의적인 행동을 하는 걸로 의심을 하는게 자연스러울 때였습니다. 그러한 이유로 웹브라우저에서는 같은 도메인이 아니면 요청 자체를 막는 선택을 하게 되었던 거죠.
그러나 기술이 발전하면서 점점 웹사이트에서 할 수 있는 일이 많아졌습니다. 단순히 문서 제공하는 용도가 아니라 무언가 어플리케이션을 만들기 시작한거죠. 그런 과도기적인 상황이 되다보니까 기존 웹브라우저 보안 정책 때문에 불편한 점들이 조금씩 생기기 시작합니다. 이러한 상황들은 많은 개발자들의 수요가 생기면서 점차 이슈로 대두되었고, JSONP라는 방식을 통해서 리소스 요청을 우회적으로 주고받는 방식이 생겨났습니다.
JSONP라는 녀석은 지금은 거의 사용되지 않기에 자세히 알아두실 필요는 없지만 간단하게 설명하면 브라우저에서 다른 도메인의 파일을 불러오는 작업이라고는 HTML script 태그에서만 가능했었는데, 이러한 점을 이용해서 데이터를 받아오는 것을 script 태그를 통해서 작업하였던 것이죠. 겉으로 보기에는 스크립트를 불러오는 식으로 실행하는데 사실 상 서버에서 데이터를 반환하는 용도로 사용하는 것이였습니다.
좋고 창의적인 방법이긴 했으나, 웹브라우저 입장에서는 이렇게 우회적인 루트로 보안을 무력화시키는걸 계속 방치할 수는 없는 노릇이었습니다.
그렇다고 이런 우회로를 버그로 판단하고 막아버리기에는 너무 많은 수요가 있었던 것이죠..
그러한 이유로 웹브라우저 측에서 '이런 우회로를 사용하지 말고 공식적인 루트를 열어줄테니 그걸 써라' 라는 취지의 무언가를 제공하게 되었고 그것이 바로 CORS의 탄생입니다.
사실 일반적으로 이 CORS 세팅을 직접 할 일은 거의 없으실거라고 생각합니다.
웹프론트엔드에서는 그냥 요청 넣을 때 CORS 옵션만 넣어주면 요즘은 요청(Request) 헤더까지 알아서 다 넣어주기도 합니다.
그리고 서버에서도 사실 마찬가지입니다. 대부분의 웹 서버에서는 간단한 옵션을 통해서 CORS를 켜고 끌 수 있게 구성을 해두었습니다.
간단하게 요약하면 일단 이렇습니다.
서로 다른 도메인의 리소스 요청을 보내고 받기 위해서는 웹프론트엔드와 서버에서 특정한 작업을 해주어야합니다.
프론트의 경우에는 그게 Request Header에 CORS 관련 옵션을 넣어주는거구요.
서버의 경우에는 Response Header에 해당하는 프론트의 요청을 허용한다는 내용을 넣어주는겁니다.
이 Header에 뭘 넣어야 할지는 정확하게 정해진 내용이 있고 그걸 따르면 끝입니다. 간단하죠?
저는 프론트엔드 입장에서 CORS 관련 이슈를 마주하였을 때의 대표적인 대응 방법을 기재하도록 하겠습니다.
대표적인 CORS 오류
Access to XMLHttpRequest at 'http://xxx' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
대표적으로 React에서 마주할 수 있는 CORS 관련 오류 메시지입니다.
저는 Proxy-middleware를 사용하여 이를 해결하는 과정을 설명드리도록 하겠습니다.
localhost의 5000번 포트의 서버와 통신을 원하고 현재 api호출 함수가 다음과 같은 상황에서 위와 같은 오류를 마주했다고 가정합니다.
axios.get( 'http://locathost:5000/api/hello').then(----
http-proxy-middleware 관련 공식 문서
$ npm install http-proxy-middleware --save
다음과 같은 명령어를 통해 http-proxy-middleware 모듈을 설치합니다.
그러고 난 뒤 src 디렉토리 내부에 setupProxy.js 파일을 생성한 뒤 다음과 같은 코드를 입력해줍니다.
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/api',
createProxyMiddleware({
target: 'http://localhost:5000',
changeOrigin: true,
})
);
};
마지막으로 기존 api를 호출하던 코드를 다음과 같이 변경해주면 CORS에 대한 적용이 완료되며 해당 이슈로부터 벗어날 수 있습니다. :)
axios.get( '/api/hello' ).then( ----
Proxy란 유저와 인터넷 사이의 중간 매개를 의미합니다. 요청을 하는 사용자의 ip를 proxy에서 변경할 수 있으므로 인터넷에서 접근하는 사람의 ip를 모르도록 해주죠. 또한 방화벽/필터 기능을 제공합니다.
이전에 한번 마주했던 부분에 대해서 잘 정리하지 않았던 것이 다소 아쉽네요.
그렇지만 이번 일을 계기로 깔끔하게 정리해놓고 이해할 수 있게되어 한편으로는 다행이라는 생각도 듭니다. 🤗
읽어주셔서 감사합니다. :)