Tech
CORS가 캐시를 만났을 때
2022. 10. 25
안녕하세요, 화해 데브옵스팀 장영석입니다. CORS가 캐시를 만났을 때
개발자라면 한 번쯤 CORS(Cross-Origin Resource Sharing) 정책 위반 에러를 경험하게 됩니다. 대부분 CORS 헤더를 추가하는 것만으로 해결할 수 있습니다. 다만 캐시를 사용할 경우 예상치 못한 상황을 경험할 수 있습니다. 어떤 문제들이 있고 어떤식으로 해결할 수 있을지 살펴보겠습니다.
CORS 란?
CORS는 Cross-Origin Resource Sharing의 약자로 HTTP 헤더를 사용해 서로 다른 출처에서 리소스를 공유하는 방식을 말합니다. 간단히 정리하면 서로 다른 출처의 리소스 접근 권한을 브라우저가 알 수 있도록 정리한 규칙이라고 할 수 있습니다. 악의를 가진 모방 사이트를 제지하기 위해서 필요한 제약사항입니다.
여기서 다른 출처란 domain, scheme, port를 포함한 것을 말합니다. 같은 도메인이라도 scheme 또는 port 가 다르다면 브라우저가 해당 요청을 다른 출처로 판단하고 CORS 정책을 기반으로 권한여부를 확인합니다. 만약 출처가 https://hwahae.co.kr인 웹 어플리케이션에서 아래 목록으로 리소스 요청을 한다면 모두 다른 출처로 판단합니다.
- http://hwahae.co.kr → 서로 다른 scheme 사용
- https://hwahae.co.kr:8080 → 서로 다른 port 사용
CORS의 동작방식
브라우저는 다른 출처로 판단한 리소스에 접근시 HTTP Origin 헤더에 출처를 추가하여 요청합니다. 요청을 받은 서버는 Origin 헤더를 기반으로 리소스 접근 허용 정책을 Access-Control-Allow-Origin 헤더에 추가하여 응답합니다. 이후 브라우저는 Access-Control-Allow-Origin 헤더값을 확인하고 현재 출처와 비교하여 권한 유무를 결정합니다.
Access-Control-Allow-Origin 외에도 좀더 상세한 정책을 위한 CORS 헤더가 있지만 이번 주제에서는 중요하지 않으므로 다루지 않습니다.
중간 캐시로 인한 CORS 에러
정적 리소스는 대부분 중간 캐시를 통해 클라이언트에 전달하는 형태로 설계됩니다. 보통 CDN을 사용하게되고 별도의 도메인을 사용하는 경우가 많습니다. 이 경우 서로 다른 출처 간 리소스 공유가 필요하게 되어 CORS 정책이 필요합니다.
예시를 위해 S3 리소스를 AWS Cloudfront를 통해 중간 캐시한다고 가정해보겠습니다. S3 bucket에 아래 메뉴에서 CORS 정책을 직접 설정할 수 있습니다.
<그림 1. S3 CORS 설정 메뉴>
아래처럼 요청 출처마다 다른 CORS 정책을 사용할 수도 있습니다. 예시에서는 https://test-a.com과 https://test-b.com 출처인 경우만 CORS를 허용하지만 허용하는 Method 등의 상세 정책은 다릅니다.
여기서 주의할 점은 클라이언트는 S3에 직접 접근하는 것이 아닌 Cloudfront를 통한다는 것입니다.
Cloudfront는 cache key를 기반으로 응답을 캐시하고 기본값은 리소스 url입니다. 기본값을 사용한다면 Origin 헤더가 다른 요청이라도 동일한 리소스에 접근한다면 같은 캐시 응답을 받게 됩니다.
<그림 2. 리소스 Url 캐시 키 예시>
https://test-a.com에서 index.html 리소스에 먼저 접근하면 최초 Cloudfront에 캐시가 없기 때문에 S3 CORS 정책 따라 GET Method를 허용하는 응답을 전달합니다. Cloudfront는 /index.html를 키로 응답값을 캐시합니다. 이후 https://test-b.com에서 동일한 리소스인 index.html에 접근할 경우 Cloudfront는 캐시된 응답값을 전달합니다. 처음 S3 CORS 정책의 설계의도와는 다르게 동작합니다. https://test-b.com에서 접근할 경우 POST Method를 허용하는 응답을 전달해야 합니다.
이 문제는 Cloudfront의 cache key 정책에 Origin 헤더를 추가하는 것으로 해결할 수 있습니다.
Legacy cache setting을 사용하는 경우
<그림 3. Cloudfront legacy cache setting을 사용할 경우>
Cache policy 인 경우
<그림 4. Cloudfront cache policy를 사용할 경우>
cache key 정책을 변경하면 처음 의도대로 CORS 정책을 사용할 수 있습니다.
<그림 5. Origin 헤더 포함 캐시 키 예시>
브라우저 캐시로 인한 CORS 에러
브라우저는 일부 태그(img, script)를 통해 리소스를 요청할 CORS를 제한하지 않습니다. CORS를 제한하지 않기에 HTTP 요청시 Origin 헤더를 추가하지 않습니다. 여기서 문제가 발생합니다. 서버가 Origin 헤더 유무에 따라 CORS 헤더 응답을 다르게 전달할 수 있습니다. 예를 들어 S3의 경우 요청에 Origin 헤더가 없을 경우 CORS 응답헤더를 전달하지 않습니다. 해당 응답을 브라우저가 로컬 캐시로 사용할 경우 문제가 발생할 수 있습니다.
브라우저에서 /image.png라는 리소스가 img 태그에 포함되어 있고 사용자 클릭 액션을 통해 fetch나 XMLHttpRequest등을 사용해 해당 리소스에 접근하여 다운로드나 응답 헤더값등을 사용한다고 가정해보겠습니다.
만약 브라우저의 로컬 캐시가 활성화되어있다면 img 태그에서 사용한 요청 응답인 CORS 헤더가 없는 캐시를 재사용하게 되고 CORS 오류가 발생할 수 있습니다.
물론 브라우저를 통해 보여주는 이미지와 다운로드 이미지 URL이 다른 경우는 문제되지 않습니다.
이 문제는 브라우저 캐시를 컨트롤 할 수 있는 HTTP 헤더 설정으로 해결할 수 있습니다. 요청 리소스가 S3에 저장되어 있다고 가정해보겠습니다.
S3는 오브젝트별 Metadata 기능을 통해 Cache-Control 응답헤더를 설정할 수 있습니다. HTTP Cache-Control 헤더를 사용해 중간 캐시와 브라우저 캐시의 동작방식을 제어할 수 있습니다. S3의 Metadata에 Cache-Control: no-store 를 추가하여 브라우저가 로컬 캐시를 사용하지 않도록 설계합니다. Metadata 설정은 콘솔과 AWS SDK, CLI를 통해 업로드시 설정할 수 있습니다.
<그림 6. S3 오브젝트의 Metadata 설정방법>
이제 Cache-Control: no-store를 설정한 리소스 접근시 브라우저는 로컬 캐시를 사용하지 않게 되어 CORS 오류가 더이상 발생하지 않습니다.
마치며
CORS 문제는 프론트엔드의 브라우저 및 요청방식, 백엔드의 CORS 설정, CDN 또는 저장소의 설정 방식 모두 관련되어 있어 특정 플랫폼에서 원인을 찾아내기 어려워 문제해결에 많은 사람의 도움과 시간이 필요합니다. 개발팀 구성원들 모두가 CORS 동작원리를 이해하고 각각의 레이어의 설정을 확인할 수 있어야 CORS 지옥에서 쉽게 빠져나올 수 있습니다.
화해팀은 테크 블로그, 주간공유, 밋업, 데브데이 등 다양한 문화를 통해 동료들과 이슈를 공유함으로서 이런 문제를 해결해 나가고 있습니다. 데브옵스 플랫폼을 포함한 다양한 개발자 분들의 채용을 적극적으로 진행하고 있으니 많은 지원 부탁드립니다.
참고자료
이 글이 마음에 드셨다면 데브옵스팀의 다른 콘텐츠도 확인해보세요!
✅ 표준 서비스 환경 자동 구축을 위한 서버 템플릿 도구 도입 사례
CORS가 캐시를 만났을 때