회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
AWS 이용 중이라면 최대 700만 원 지원받으세요
국내 유명 IT 기업은 한국을 넘어 세계를 무대로 할 정도로 뛰어난 기술과 아이디어를 자랑합니다. 이들은 기업 블로그를 통해 이러한 정보를 공개하고 있습니다. 요즘IT는 각 기업의 특색 있고 유익한 콘텐츠를 소개하는 시리즈를 준비했습니다. 이들은 어떻게 사고하고, 어떤 방식으로 일하는 걸까요?
회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
회원가입을 하면
성장에 도움이 되는 콘텐츠를
스크랩할 수 있어요!
확인
국내 유명 IT 기업은 한국을 넘어 세계를 무대로 할 정도로 뛰어난 기술과 아이디어를 자랑합니다. 이들은 기업 블로그를 통해 이러한 정보를 공개하고 있습니다. 요즘IT는 각 기업의 특색 있고 유익한 콘텐츠를 소개하는 시리즈를 준비했습니다. 이들은 어떻게 사고하고, 어떤 방식으로 일하는 걸까요?
이번 글은 국내 화장품 시장의 정보 비대칭 문제를 해결하고 소비자 중심의 뷰티 시장을 만들어 가고 있는 ‘화해’의 데브옵스팀의 이야기입니다. CORS(Cross-Origin Resource Sharing) 정책 위반 에러에 대해 문제점과 해결 방법을 소개하고 있습니다.
안녕하세요, 화해 데브옵스팀 장영석입니다.
개발자라면 한 번쯤 CORS(Cross-Origin Resource Sharing) 정책 위반 에러를 경험하게 됩니다. 대부분 CORS 헤더를 추가하는 것만으로 해결할 수 있습니다. 다만 캐시를 사용할 경우 예상치 못한 상황을 경험할 수 있습니다. 어떤 문제들이 있고 어떤 식으로 해결할 수 있을지 살펴보겠습니다.
CORS는 Cross-Origin Resource Sharing의 약자로 HTTP 헤더를 사용해 서로 다른 출처에서 리소스를 공유하는 방식을 말합니다. 간단히 정리하면 서로 다른 출처의 리소스 접근 권한을 브라우저가 알 수 있도록 정리한 규칙이라고 할 수 있습니다. 악의를 가진 모방 사이트를 제지하기 위해서 필요한 제약사항입니다.
여기서 다른 출처란 domain, scheme, port를 포함한 것을 말합니다. 같은 도메인이라도 scheme 또는 port 가 다르다면 브라우저가 해당 요청을 다른 출처로 판단하고 CORS 정책을 기반으로 권한 여부를 확인합니다. 만약 출처가 https://hwahae.co.kr인 웹 애플리케이션에서 아래 목록으로 리소스 요청을 한다면 모두 다른 출처로 판단합니다.
브라우저는 다른 출처로 판단한 리소스에 접근 시 HTTP Origin 헤더에 출처를 추가하여 요청합니다. 요청을 받은 서버는 Origin 헤더를 기반으로 리소스 접근 허용 정책을 Access-Control-Allow-Origin 헤더에 추가하여 응답합니다. 이후 브라우저는 Access-Control-Allow-Origin 헤더값을 확인하고 현재 출처와 비교하여 권한 유무를 결정합니다.
Access-Control-Allow-Origin 외에도 좀 더 상세한 정책을 위한 CORS 헤더가 있지만 이번 주제에서는 중요하지 않으므로 다루지 않습니다.
정적 리소스는 대부분 중간 캐시를 통해 클라이언트에 전달하는 형태로 설계됩니다. 보통 CDN을 사용하게 되고 별도의 도메인을 사용하는 경우가 많습니다. 이 경우 서로 다른 출처 간 리소스 공유가 필요하게 되어 CORS 정책이 필요합니다.
예시를 위해 S3 리소스를 AWS Cloudfront를 통해 중간 캐시한다고 가정해 보겠습니다. S3 bucket에 아래 메뉴에서 CORS 정책을 직접 설정할 수 있습니다.
아래처럼 요청 출처마다 다른 CORS 정책을 사용할 수도 있습니다. 예시에서는 https://test-a.com과 https://test-b.com 출처인 경우만 CORS를 허용하지만 허용하는 Method 등의 상세 정책은 다릅니다.
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"GET"
],
"AllowedOrigins": [
"https://test-a.com"
],
"ExposeHeaders": [],
},
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"POST"
],
"AllowedOrigins": [
"https://test-b.com"
],
"ExposeHeaders": [
"Content-Length"
]
}
]
여기서 주의할 점은 클라이언트는 S3에 직접 접근하는 것이 아닌 Cloudfront를 통한다는 것입니다.
Cloudfront는 cache key를 기반으로 응답을 캐시하고 기본값은 리소스 url입니다. 기본값을 사용한다면 Origin 헤더가 다른 요청이라도 동일한 리소스에 접근한다면 같은 캐시 응답을 받게 됩니다.
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 헤더를 추가하는 것으로 해결할 수 있습니다.
cache key 정책을 변경하면 처음 의도대로 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를 통해 업로드 시 설정할 수 있습니다.
이제 Cache-Control: no-store를 설정한 리소스 접근 시 브라우저는 로컬 캐시를 사용하지 않게 되어 CORS 오류가 더 이상 발생하지 않습니다.
CORS 문제는 프론트엔드의 브라우저 및 요청 방식, 백엔드의 CORS 설정, CDN 또는 저장소의 설정 방식 모두 관련되어 있어 특정 플랫폼에서 원인을 찾아내기 어려워 문제 해결에 많은 사람의 도움과 시간이 필요합니다. 개발팀 구성원들 모두가 CORS 동작원리를 이해하고, 각각의 레이어의 설정을 확인할 수 있어야 CORS 지옥에서 쉽게 빠져나올 수 있습니다.
<참고 자료>
<원문>