CORS와 Jsonp에 대해서
웹팩의 빌드 파일을 공부하다가 빌드된 runtime파일에서 jsonp를 사용하는데 이게 어떤거고 왜 사용해야하는지 몰라서 정리를 해보려고합니다. jsonp가 발전된 형태가 cors라고 하니 같이 정리해보겠습니다.
SOP
자바스크립트로 내가 만든 서버가 아닌 다른 서버로 요청을 보내게 되면 에러가 났던 경험 다들 한번씩 있으실겁니다. 보안상의 이유로 자바스크립트는 동일 출처(같은 도메인 주소를 가진 서버)로만 데이터를 주고받을수 있습니다.
이것을 동일 출처 정책(Same-Origin Policy)라고 합니다.
JSONP란
2009년에 CORS가 나오기 전까지 다른 도메인에 비동기 요청을 하기 위해 사용되던 방법입니다. 자바스크립트 파일 내에서 다른 도메인에 데이터를 요청할 경우 SOP에 위배되어 에러가 나게 됩니다. 예를들어, https://test.com에서 요청을 보내면 https://test.com도메인을 가진 서버에만 요청을 보낼 수 있습니다.
하지만, html파일내에 있는 <script src=”https://test.com”></script>태그를 이용하면 SOP를 피해갈수 있습니다. https://localhost에서 https://test.com(서로 다른 도메인)의 데이터를 가져올 수 있는거죠. 다만 조건이 있습니다.
- 서버에서 Json형태로 데이터를 내려줘야함.
- script태그로 받아온 Json 데이터를 브라우저 어딘가에 저장해놔야 쓸 수 있음.
받아온 Json데이터를 어디에 저장해야 하는가?
단순히 var result = <script src=”…”></script>; 와 같이 변수에 값을 할당하는 방식은 사용이 불가능하기 때문에 다른 방법이 필요합니다. 서버의 Json데이터를 어떻게 브라우저 까지 가져오는지 살펴보겠습니다.
// 브라우저
<script src="https://test.com"></script>
html 하단에 위와 같은 script태그가 있습니다.
// 서버에서 내려줌
myCallback({
title : 'hello world!'
})
서버에서는 단순히 Json데이터를 내려주는게 아니라 myCallback()으로 감싸서 내려줍니다. 왜냐면 <script>태그의 결과물 자체를 변수에 저장할 수 없기 때문이죠.
// 브라우저<script>
function myCallback(json){
window.result = json;
}
</script><script src="https://test.com"></script>
-> 다음과 같이 변환됨.
myCallback({
title : 'hello world!'
})
서버에서 내려준 콜백함수가(myCallback) 미리 html에서 정의가 되어 있어야합니다.
이런식으로 서버에서 내려준 json 데이터를 콜백함수를 통해서 브라우저의 전역변수인 window객체에 넣어서 사용할 수 있게 되었습니다. 이러한 방법은 CORS가 2009년에 나오기전까지 사용되던 방법인데 이제는 보안상의 이슈로 더이상 사용되지 않는다고 합니다.
CORS (Cross Origin Resource Sharing)
HTTP요청은 기본적으로 다른 도메인에도 사용이 가능합니다. img태그로 외부 이미지를 불러올 수 있으며 link태그로 외부 css를 불러올 수 있고, script태그로 외부 js라이브러리를 불러올 수 있습니다.
다만, 내가 작성한 js파일 내에서 다른 도메인으로 비동기 ajax 호출을 하게 된다면 SOP(동일출처정책)에 위배 됩니다.
CORS가 나오기 이전(2009년 이전)만 해도 다른 도메인에 비동기 요청을 보낼일이 많이 없었지만 요즘에는 그렇지 않습니다. 시대가 변하면서 다른 출처에 대해서도 자유롭게 HTTP호출을 할 수 있게 변화해야 했습니다. 그래서 나온것이 CORS(직역 : 다른 출처의 리소스를 공유한다)입니다.
CORS의 종류 4가지
1. Simple Request
아래 조건을 만족하는 HTTP 요청은 Simple Request로 처리됩니다. 브라우저와 서버가 요청과 응답을 한번씩만 주고 받습니다.
- GET, POST, HEAD 중 하나의 HTTP Method로 요청한 경우
- POST인 경우 Content-type이 셋 중 하나일 경우
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
2. Preflight Request (예비요청)
위 조건에 해당하지 않으면 브라우저는 Preflight Request방식으로 동작합니다. 서버로 예비요청과 응답을 한번 주고 받고 본요청을 한번더 주고받는 형태이죠.
HTTP Method 중 OPTIONS를 사용해서 예비요청을 서버로 보내, 인증 절차를 거치고 본 요청을 처리하는 방식입니다. 예비요청을 받은 서버는 HTTP 응답헤더에 허용하고 싶은 부분만 Access-control- 로 지정해저 브라우저로 전달하면 본요청시 브라우저는 서버에서 허용한 부분만 요청할 수 있습니다.
3. Credential (자격 증명)
브라우저의 쿠키를 다른 도메인의 서버로 전달하기 위해서는 이 방식을 사용해야 합니다. 클라이언트에서 비동기 요청시 xhr.withCredentials = true 로 설정해야 합니다.
또한 서버에서도 Access-Control-Allow-Credentials: true로 설정한뒤 Access-Control-Allow-Origin의 헤더로는 *가 아닌 다른 구체적인 도메인(https://test.com)이 와야 정상 동작합니다. 모든 Origin에 대해서 자격증명을 허용하면 보안상 이슈가 생겨서 이렇게 만들어 놓은 것 같습니다. 딱 “내가 알고있는 안전한 하나의 도메인에만 쿠키를 허용하겠다”라는 의미죠.
4. Non Credential(비 자격 증명)
기본적으로 브라우저는 Non Credential모드로 동작합니다. 따라서 withCredentials 를 true로 설정하지 않으면 이 방식대로 동작합니다.
위 4가지 방식중 하나로 서로 다른 도메인을 가진 클라이언트와 서버가 안전하게 HTTP 통신 할 수 있게 되는 것입니다.
예시
Node.js 서버에 CORS를 설정해주는 부분을 살펴봅시다.
const corsOptions = {
origin: 'http://localhost:3000', // 허락하고자 하는 요청 주소
credentials: true, // 위 origin과는 쿠키를 주고 받을 수 있습니다.
};
서버는 현재 localhost:8000에 켜있습니다. 이런식으로 구체적인(*이 아닌) 딱 하나의 클라이언트(origin, localhost:3000)와 서버(localhost:8000)은 쿠키를 주고 받을 수 있습니다.