...
브라우저 캐시로 인한 CORS 문제
CORS(Cross-Origin Resource Sharing)는 서로 다른 출처(Origin)의 리소스를 공유하고 싶을때 사용하는 정책을 말한다.
기본적으로 브라우저는 SOP(Same Origin Policy) 정책을 따르기 때문에 외부 리소스에 대해서 차단한다. 하지만 인터넷은 여러 사람들에게 오픈된 환경이고, 이런 환경에서 웹페이지에서 다른 출처에 있는 리소스를 가져와 사용하는 일은 매우 흔한 일이라 모든 외부 리소스를 무턱대고 막으면 지금처럼 웹이 발전하지 않았을 것이다. 따라서 외부 리소스라도 허용 가능한 예외 사항을 두었는데 그것이 CORS 정책이다.
요청 방식에 따라 다른 CORS 발생 여부
이때 브라우저는 HTTP 요청에 대해서 어떤 방식으로 요청을 하느냐에 따라 CORS를 자동으로 허용하기도 막기도 한다. 예를들어 <img> 태그와 같은 인라인으로 리소스를 요청하면 다른 출처의 리소스라도 검사 없이 자동 통과된다. 그러나 자바스크립트 Ajax 요청일 경우 어김없이 차단되어 버린다.
요청 방식에 따라 다른 CORS 발생 여부를 좀 더 이해하기 쉽게 아래 html 코드를 직접 작성하고 테스트 해보자. 똑같은 서버 도메인으로 부터 check.svg 이미지를 가져오지만 결과는 다르게 된다.
※ 참고로 해당 호스팅 서버는 cors 설정이 되어있지 않는 서버라고 가정해보자.
<img src="https://third-party-test.glitch.me/check.svg" alt="이미지">
<script>
fetch('https://third-party-test.glitch.me/check.svg')
.then(response => response.blob())
.then(imgBlob => {
const imageObjectURL = URL.createObjectURL(imgBlob); // 응답 받은 이미지를 blob 객체로 변환
const img = document.createElement('img'); // 이미지 태그를 생성하고
img.src = imageObjectURL; // 이미지 경로를 설정한뒤
document.body.append(img); // html에 추가
})
</script>
페이지 결과를 보면 이미지가 하나만 나타나는 걸 볼 수 있을 것이다.
브라우저 개발자 도구의 Network 창을 보면 check.svg 이미지에 대해서 두번 요청은 했지만 자바스크립트 fetch Type으로 요청한 것이 Status가 CORS error 임을 볼수가 있다.
fetch 요청 처리에 대한 헤더 목록을 보면 그 이유를 알 수 있는데, 클라이언트가 자신의 Origin을 요청 헤더에 넣어 서버에 전달했지만, 서버에선 별다른 액션 없이 Access-Control-Allow-Origin 헤더를 응답해주지 않았고, 이를 브라우저가 감지하여 사단에 차단한 것이다. 여기서 오해하지 말아야 할 점이 서버는 리소스를 정상 응답했지만 브라우저가 cors 관련 헤더 없다고 차단해 버린 것이다.
반면 브라우저는 <img>, <video> 와 같은 일부 미디어 태그를 통해 리소스를 요청할 CORS를 제한하지 않는다.그래서 HTTP 요청시 Origin 헤더를 추가하지 않고, 브라우저도 별다른 검사 없이 통과하게 해주는 것이다.
캐시에 저장된 리소스를 그대로 불러올 경우
그런데 이 CORS를 제한하지 않고 받은 리소스를 브라우저가 로컬 캐시에 저장하고 재활용 할 경우 문제가 발생하게 된다.
예를 들어 아래와 같이 이미지를 요청 할경우 문제없이 서버로 부터 외부 리소스를 받는다.
※ 참고로 해당 호스팅 서버는 cors 설정이 되어있는 서버이기 때문에 자바스크립트 ajax 요청에도 정상적으로 응답하게 된다.
<img src="https://tistory4.daumcdn.net/tistory/5927418/skin/images/sample.png" alt="이미지">
<script>
fetch('https://tistory4.daumcdn.net/tistory/5927418/skin/images/sample.png', { mode: 'cors' })
.then(response => response.blob())
.then(imgBlob => {
const imageObjectURL = URL.createObjectURL(imgBlob);
const img = document.createElement('img');
img.src = imageObjectURL;
document.body.append(img);
})
</script>
이번에는 자바스크립트 fetch 요청을 3초 뒤에 하라고 setTimeout() 메서드를 통해 설정해보자. 이렇게 하는 이유는 이미지 태그로 요청한 sample.png 이미지가 브라우저 로컬 캐시에 저장하도록 시간을 주기 위해서 이다. 그러면 3초 뒤에 똑같은 이미지 URL을 요청할 경우 브라우저는 자동으로 최적화를 위해 서버에 요청을 때리는 것이 아닌 캐시 저장소에서 리소스를 가져오게 된다.
<img src="https://tistory4.daumcdn.net/tistory/5927418/skin/images/sample.png" alt="이미지">
<script>
setTimeout(() => {
fetch('https://tistory4.daumcdn.net/tistory/5927418/skin/images/sample.png', { mode: 'cors' })
.then(response => response.blob())
.then(imgBlob => {
const imageObjectURL = URL.createObjectURL(imgBlob);
const img = document.createElement('img');
img.src = imageObjectURL;
document.body.append(img);
})
}, 3000);
</script>
그런데 결과는 CORS 에러가 뜬다. 분명 서버에서 모든 출처를 허용하는 access-control-allow-origin: * 헤더를 설정하였고, setTimeout() 메서드를 전 까지는 문제없이 잘 받아왔는데 말이다.
리소스에 대한 요청 / 응답 헤더를 보면 왜 그런지에 대한 유추가 가능하다.
앞서 말했듯이 <img> 태그로 불러오는 인라인 리소스 요청일 경우엔 요청 헤더에는 Origin 헤더가 없다. 따라서 서버에서도 Access-Control-Allow-Origin 헤더를 실어 보내지 않게되고, 이 상태 그대로 캐시에 적재되게 된다.
그리고 3초 뒤에 똑같은 리소스 URL을 요청했을때 캐시 저장소에 있는 리소스를 가져오는데, 응답 헤더에 Access-Control-Allow-Origin 헤더가 없기 때문에 아래와 같이 No ' Access-Control-Allow-Origin' header 라고 에러 메세지를 내뿜는 것이다.
캐시로 인한 CORS 에러 해결방법
crossorigin 속성 할당하여 강제 CORS 검사 행하기
해결 방법은 아래와 같이 <img> 태그의 속성으로 crossorigin 을 할당해 주면 된다.
<img crossorigin src="https://tistory4.daumcdn.net/tistory/5927418/skin/images/sample.png" alt="이미지">
<script>
setTimeout(() => {
fetch('https://tistory4.daumcdn.net/tistory/5927418/skin/images/sample.png', { mode: 'cors' })
.then(response => response.blob())
.then(imgBlob => {
const imageObjectURL = URL.createObjectURL(imgBlob);
const img = document.createElement('img');
img.src = imageObjectURL;
document.body.append(img);
})
}, 3000);
</script>
crossorigin 속성
<audio> , <img> , <link> , <script> 및 <video> 와 같은 미디어 요소 의 crossorigin 속성은 요소가 교차 출처 요청을 처리하는 방법을 정의하는 속성이다. 보통은 클라이언트에서 쿠키나 인증 헤더를 실어 서버에 보낼때 사용되는 속성이지만, 이것을 태그 속성으로 명시하면 인라인 리소스 요청이든 뭐든 강제적으로 CORS 정책을 따르게 하여 리소스에 대한 엑세스 권한을 요청하게 한다.
개발자 도구의 헤더 목록을 보면 더 감이 잡힐 것이다. 비록 인라인 리소스 요청이지만 요청 헤더에 Origin 헤더가 담긴걸 볼 수 있고 서버도 이에 반응하여 cors 헤더를 응답함을 볼 수 가 있다.
따라서 3초 뒤에 행해지만 자바스크립트 Ajax 요청 역시 캐시에 리소스를 가져오더라도, 이미 캐시 저장소에 있는 리소스는 Access-Control-Allow-Origin 헤더가 설정된 리소스 이기 때문에 CORS 관문 검사에 통과되는 것이다.
crossorigin 속성이 있는데 서버 CORS 허용이 안될 경우
반대로 만일 서버에서 별다른 cors 헤더 설정을 하지 않았을 경우 <img crossorigin src> 인라인 요청도 결국 cors 에러 로그가 나타나게 된다. 왜냐하면 crossorigin 속성은 강제로 CORS 검사 관문을 행하기 때문이다.
정리하자면, 서버에 Origin 허용이 된 상태에서 동일한 리소스에 대한 중복 요청을 cors를 통해 가져올 경우 위의 엇갈림을 조심하면 된다. 또한 브라우저 로컬 캐시 뿐만 아니라 이밖에도 프록시(Proxy)나 CloudFront와 같은 CDN 서비스를 이용할때도 CORS 캐시 문제가 따라오게 된다. 보통 클라우드의 CDN 서비스일 경우 이러한 경우를 대비해 별도로 Cache Key 설정을 제공하니 이에 대해서 검색해보길 바란다.
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.