로컬에서 CORS policy 관련 에러가 발생하는 이유

takeknowledge·2019년 12월 22일
12
post-thumbnail

🚀 발단


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>CORS</title>
</head>
<body>
    <script type="module" src="js/module.js"></script>
</body>
</html>

위와 같은 html 파일을 로컬환경에서 크롬 브라우져로 실행시켰더니

Access to script at 'file:///C:/경로/js/module.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.

이와 같은 에러 메세지가 발생하였습니다.

🛫 해결방안


비슷한 에러를 몇 번 겪어 봤기에 해결은 금방 했습니다.
visual studio code 기준으로 설명해 보자면 방법은 아래와 같습니다.

먼저 터미널을 엽니다.

http-server가 없다면

npm install http-server -g

위 키워드로 http-server를 전역으로 설치해 준 다음

npx http-server

위 명령어로 http-server를 실행시켜 해당 폴더를 서버에 올립니다.

http://127.0.0.1:8080

위 URI로 접속해서 에러가 사라진 것을 확인합니다.

만약 서버가 올라가는 포트를 기본인 8080이 아닌 다른 포트로 실행하고 싶다면

npx http-server -p 원하는 포트번호

로 서버를 실행해 해당 포트로 접속하면 됩니다.

👀 왜?

그러나 매번 'CORS 란게 있어서 그렇고 서버에 올리면 해결된다' 정도로 넘어갔기 때문에 이번 기회에 제대로 한번 알아봐야 겠단 생각을 했습니다. 일단 이리 저리 검색해보니 핵심은 SOP였습니다.

🔍 SOP (Same Origin Policy - 동일 출처 정책)

SOP는 '동일 출처 정책' 입니다. 어떤 출처(origin)에서 불러온 문서나 스크립트가 다른 출처에서 가져온 리소스와 상호작용하는 것을 제한하는 브라우저의 보안 방식입니다.

이 때 다른 출처와 같은 출처를 구분하는 기준은 URI의 '프로토콜 호스트 포트 가 같은가' 입니다.

scheme(protocol)host(port)resource
http://www.example.com(:80)/folder/file.html

프로토콜, 호스트, 포트가 가르키는 부분은 위와 같습니다. (포트는 주로 생략됩니다)
위에서 포트까지의 부분이 같다면 같은 출처,다르면 다른 출처로 간주됩니다.

그러나 이러한 정책이 모든 방식의 요청에 적용 되는 것은 아닙니다.

예를 들어

  • <img> 태그로 다른 도메인의 이미지 파일을 가져오거나
  • <link> 태그로 다른 도메인의 CSS를 가져오거나
  • <script> 태그로 다른 도메인의 javascript를 가져오는 것
  • 그 외에도 <video> <audio> <object> <embed> <applet> 태그

에는 동일 출처 정책이 적용되지 않습니다.

SOP는 script에서 XMLHttpRequestFetch API를 사용해 다른 출처에 리소스를 요청할 때 적용됩니다.

결국 CORS 에러로 불리는 에러가 발생하는 이유는 이 SOP가 적용되는 방식으로 다른 출처의 자원에 접근하려 했기 때문이었고 CORS는 이런 경우 cross-origin HTTP 요청을 실행하여 액세스 권한을 부여하도록 하는 매커니즘을 가르키는 말이었습니다.

CORS를 적용하는 방법은 길고 본 포스팅의 핵심에서 벗어나 있어서 참고 사이트만 남깁니다
Cross Origin Resource Sharing - CORS

🤷‍♂️ 근데 그래서 로컬에선 왜?

그런데 SOP에 관해 공부하고 나서는 더 혼란스러워지기 시작했습니다. 문제가 된 부분은

<script type="module" src="js/module.js"></script>

인데 위 부분은 SOP에 위배될 게 없어 보였기 때문입니다.

  1. XMLHttpRequestFetch API를 사용해 무언가를 요청한 것도 아니고
  2. 동일경로/js/module.js를 요청한 것이니 이건 다른 출처의 자원 기준에 부합하지도 않았으니까요.

'뭐지?'

결국 또 한참을 검색할 수 밖에 없었지만 다행히 답은 찾았습니다.

✌ 정답

<script type=module>의 특성

먼저 MDN의 javascript modules에 관한 설명에 따르면 문제가 된 부분같이 typemodule로 설정한<script> 태그가 포함된 HTML 파일을 로컬에서 로드할 경우 자바스크립트 모듈 보안 요구사항으로 인해 CORS 오류가 발생한다고 합니다. 그 때문에 ajax로 요청한 것임 아님에도 불구하고 CORS 오류가 발생했던 것이죠.

🍜 로컬의 리소스를 요청할 때의 origin(출처)은 null이다

두번째 질문에 대한 답은, 알고 보니 발생하는 에러 문구에 힌트가 있었습니다.

코드에서 알 수 있듯 c:/경로/index.html에서 c:/경로/js/module.js. 즉 같은 경로의 자원을 요청하는데 에러 메세지엔 보시다시피 origin, 즉 출처가 null 로 넘어온 script에 대한 접근이 CORS 정책에 따라 제한되었다고 나와있습니다. 정말 출처가 null로 넘어간건지 확인하기 위해 네트워크 탭을 보면

실제로 그렇단 걸 확인할 수 있습니다. 이를 바탕으로 검색해보니 브라우저는 (브라우저 별로 약간은 상이하지만) 웹에서 로컬 파일에 접근하지 못하도록 하기 위해 이런 식의 방법을 사용한다고 합니다.

즉 c:/경로/index.html에서 ajax로 c:/경로/js/module.js에 리소스를 요청한 건 동일 경로의 리소스를 요청한 것이 아니고 c:/경로/index.html에서 null/js/module.js로 리소스를 요청한 것이 되어 CORS에러가 발생한 것입니다.

그렇기 때문에 서버에 올려 프로토콜 호스트 포트를 같게 만들면 CORS 에러가 해결되었던 것이죠.
이 정답 챕터를 간단히 정리하자면 아래와 같을 것입니다.

📃 정리

  1. <script type=module> 은 로컬에서 실행시 자바스크립트 모듈 보안 요구로 인해 CORS 에러가 발생한다
  2. 로컬시스템에서 로컬 파일 리소스를 요청할 때는 origin(출처)이 null로 넘어가기 때문에 CORS에러가 발생한다.

📚 참조


MDN - Same-Origin policy
google code - browsersec - Part2.wiki - Same-origin policy
슭의 개발 블로그 - file URI와 same-origin policy
What is CORS ( Cross-Origin Resource Sharing ) ?

profile
주로 개발을 하고, 가끔 글을 쓰며, 어쩌다 랩을 합니다

6개의 댓글

comment-user-thumbnail
2019년 12월 24일

잘 읽었습니다 ㅎ

답글 달기
comment-user-thumbnail
2020년 2월 13일

헉 정답이... 정답부분점 고쳐주실수있나요!!

1개의 답글
comment-user-thumbnail
2020년 3월 17일

정답부분에 버그가 ㅋㅋㅋㅋ 순간 정답은 없나 혼란스러웠습니다

1개의 답글
comment-user-thumbnail
2020년 10월 20일

알기쉽게 정리해주셔서 감사합니다 ㅎㅎ

답글 달기