S3 이미지를 Canvas API에서 사용할 경우 발생하는 CORS 문제 해결 방법

Yukicow·2024년 2월 23일
0

이미지가 매우 빈번하게 사용되는 프로젝트를 개발하는 도중 발생한 CORS 문제를 어떻게 해결했는지 설명해 보고자 한다.

처음에 근복적인 원인을 해결 했음에도 어떤 실수가 있어서 계속 문제가 발생했는지 부터 차근차근 설명하겠다.

문제1. No 'Access-Control-Allow-Origin' header is present on the requested resource.


가장 먼저 맞딱드린 문제는 콘솔에 표시되는 위의 CORS 에러였다.

이 문제의 근복적인 원인은 말 그대로 Access-Control-Allow-Origin 헤더가 응답에 포함되어 있지 않아서 Canvas API가 내부적으로 이미지를 사용할 수 없어 발생하는 문제였다.

https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image

As soon as you draw into a canvas any data that was loaded from another origin without CORS approval, the canvas becomes tainted. A tainted canvas is one which is no longer considered secure, and any attempts to retrieve image data back from the canvas will cause an exception to be thrown.

대충 CORS 승인 없이 Canvas API에서 이미지를 사용할 경우 제대로 동작하지 않는다는 내용이다.

이를 해결하기 위해서 제공되는 응답헤더가 존재하는데 바로 Access-Control-Allow-Origin이다.

Access-Control-Allow-Origin헤더는 이미지 파일에 대한 원본 간 액세스를 허용하도록 구성된 헤더라고 한다.

그렇다면 S3로부터 Access-Control-Allow-Origin 헤더가 담긴 응답을 받아야 하는데, S3는 요청에 Origin 헤더가 포함되어 있지 않으면 이 Access-Control-Allow-Origin 헤더를 응답에 담지 않는다고 한다.

즉, 이미지를 요청할 때 Origin 헤더를 포함시키면 Access-Control-Allow-Origin 헤더가 응답에 포함될 것이고 Canvas API는 문제 없이 동작할 것이다.

HTML에는 이러한 CORS 문제를 생각해서 img에 crossorigin 속성을 만들어 두었다.

crossorigin 속성을 설정하면 요청에 Origin 헤더가 포함된다.

Canvas API에서 사용될 이미지를 가져오는 부분에서 이러한 crossorigin 속성을 부여해 주면 요청에 Origin을 포함시킬 수 있다.

crossorigin 속성에는 anonymoususe-credentials이 사용될 수 있고, 공백이나 두 값을 제외한 모든 문자열은 anonymous로 처리된다.


const img = new Image();
img.crossOrigin = "anonymous";

위와 같은 코드를 추가함으로써 이미지를 사용할 떄, CORS에러가 발생하지 않는다.



문제2. 캐시된 이미지를 사용하면서 발생하는 문제

위와 같이 설정을 했음에도 필자의 사이트는 제대로 동작하지 않았다.

위 사진이 crossorigin 속성을 추가하여 정상적으로 S3로부터 응답을 받을 경우의 나타날 모습이다.

그러나 실제는 제대로 이미지가 불러와지지 않았고, 콘솔에는 No 'Access-Control-Allow-Origin' header is present on the requested resource.가 계속해서 찍힐 뿐이었다.


이러한 문제의 원인은 매우 쉬운 곳에 있었다. Access-Control-Allow-Origin 헤더가 없는 응답 이미지를 캐시에서 가져와 재사용하기 때문이었다.

근데, 분명히 crossorigin 속성을 추가했는데, 왜 Access-Control-Allow-Origin 헤더가 없는 이미지가 캐시된거지? 라는 생각이 들 수 있다.

만약 위와 같은 이미지를 Canvas API를 사용하는 페이지 외에 다른 곳에서도 사용하고 있다면 발생하는 문제이다.


필자의 경우 위와 같은 페이지들에서 이미지가 사용되고 있었다.

해당 페이지 들에서는 Canvas API를 사용하지 않기 때문에 crossorigin 속성은 당연히 추가하지 않았고, 해당 페이지들을 거치며 Access-Control-Allow-Origin 헤더가 없는 이미지가 응답으로 반환되고 캐시되었던 것이다.

그리고 실제 Cavnas API에서 사용되는 페이지에서 이미지를 요청할 때에는 캐시된 이미지를 사용하기 때문에, Access-Control-Allow-Origin 헤더가 없어 CORS 에러가 발생한 것이다.


해결

문제의 원인을 알았으니 해결 방버에 대해 알아 보자.

위와 같은 문제를 해결하기 위한 방벙느 여러가지가 있다.


1. S3에서 객체에 Cache-Control을 설정

이미지를 요청할 때 캐시된 이미지가 아닌 매번 S3로 요청한다면 어떤 페이지에서든 문제 없이 동작할 것이다.

하지만 이 방법에는 치명적인 단점이 있다. 객체가 저장될 때 Cache-Control을 설정되게 할 수가 없어 매번 수동으로 바꿔 주어야 한다는 것이다.

( 필자가 방법을 모르는 것일 수도 있다 )

또한, 캐시를 허용하지 않는다는 것은 모든 이미지 요청마다 네트워크를 타게 되고 지연이 발생할 수 있다.

그래서 이 방법은 이미지가 자주 업로드 되는 형식의 프로젝트에서는 추천하지 않고, S3에 미리 저장된 이미지를 사용하는 경우에 좋을 것 같다.


2. CloudFront 사용

CloutFront를 사용하여 응답에 항상 Access-Control-Allow-Origin가 포함되도록 하는 방법이 있다.

하지만 이 방법도 추가적으로 CloutFront를 사용해야 한다는 문제와, 러닝 커브가 존재한다는 단점이 있다.

CloudFront에 대한 이해가 높다면 적용해 볼만 하다고 생각한다.


3. 이미지를 요청할 때 매번 crossorigin 설정하기

필자가 사용하는 방식이다.

CloudFront를 사용하기 어려운 상황이라면 CloudFront처럼 동작하게 만들면 된다.

위에서 필자는 두 개의 추가적인 페이지에서 이미지를 사용하고 있었고, 이미지를 호출하는 페이지들에서 crossorigin을 사용하지 않기 때문에 발생하는 문제였다.

그렇다면 해당 페이지들에서 이미지를 요청할 떄 crossorigin를 함께 설정해 주면 문제가 없다는 뜻이기도 하다.

두 페이지에서 요청이 일어나 캐시되더라도 응답에 Access-Control-Allow-Origin 헤더가 포함되어 있으니 재사용하더라도 문제가 없다.

이 방식에도 문제는 있다.

개발자가 매번 Canvas API에서 사용되는 이미지가 똑같이 사용되는 페이지들 마다 crossorigin을 추가해야 한다는 것을 인지하고 개발해야 한다.

하지만 큰 문제가 안 되는게, 생각하는게 어려우면 그냥 S3 이미지를 불러올 때 모두 crossorigin 속성을 추가하면 된다.

네트워크 비용이 아주아주 살짝 증가할 수는 있지만 이는 CloudFront를 사용해도 어차피 마찬가지이다.



정리

혹시나 Canvas API를 이용해 이미지를 많이 활용하게 되는 프로젝트를 하게 된다면, 또는 그런 사람들이 있다면 필자와 비슷한 상황을 겪게 될 수 잇다.

S3를 사용할 때 CORS 발생 시에 대한 해결 방법을 정리한 글들은 많았으나, 필자처럼 이미지가 다른 페이지에서 사용되어 캐시된 문제로 인한 경우를 콕 집어서 정리한 글을 못 봤던 것 같다.

CORS 문제 자체를 해결하더라도 이런 연계된 상황으로 인해 예상치 못 한 CORS가 발생할 수도 있으니 조심하는게 좋을 것 같다.

profile
자료를 찾다 보면 사소한 부분에서 궁금한 부분이 생기도 한다. 똑같은 복붙식 블로그 때문에 시간만 낭비되고 시원하게 해결하지 못 하는 경우가 많았다. 그런 부분들까지 세세하게 고민하고 함께 해결해 나가고자 글을 작성한다. 혼자서 작성하는 블로그가 아닌 함께 만들어 가는 블로그이다. ( 지식 공유를 환영합니다. )

0개의 댓글