AWS S3는 Simple Storage Service를 줄여 S3라고 부른다.
이 S3는 여러 용도로 쓰이는데 대표적으로 아래 세가지 용도로 주로 사용된다.
이번에 해결한 이슈는 3
번 케이스와 같이 사용하다가 발생한 이슈이다.
우선 CORS 라는 개념을 알고 가야 한다.
CORS란 Cross Origin Resource Sharing 을 의미한다.
우리말로 번역해보면 교차 출처 리소스 공유
라고 번역 할 수 있다.
CORS는 웹 브라우저에서 보안상의 이유로 도입되었는데. 현재 사용자가 접속한 웹 애플리케이션이 다른 출처의 리소스를 불러올 때, Access-Control-Allow-Origin
헤더를 보내주지 않으면 브라우저가 그 리소스를 거부하는 보안 정책(?) 이라고 할 수 있다.
풀어서 쉽게 설명하면 다음과 같다.
현재 유저는 https://aaaaaa.com
를 브라우저를 통해 접속했다.
이 https://aaaaaa.com
은 별도의 API 및 리소스 서버를 두어서, 웹 애플리케이션에 필요한 이미지나 텍스트들을 https://bbbbbb.com
에서 불러온다고 해보자.
즉 현재 유저가 보고있는 웹 애플리케이션의 도메인과, 실제 리소스를 불러와서 사용할 서버의 도메인이 다르기때문에 웹 브라우저는 Access-Control-Allow-Origin
헤더가 리소스 서버의 응답에 적절히 들어있지 않다면 사용하기를 거부한다.
개발하다 한번쯤은 겪는 CORS 에러...
이 에러를 해결하기 위한 방법은 매우 간단한데, 응답을 보내주는 서버측에서 Access-Control-Allow-Origin
헤더에 적절한 값을 담아서 보내주면 된다.
이런식으로 서버측에서 access-control-allow-origin 헤더에 적절한 응답을 넣어주면 된다.
보통은 개발의 편의성을 위해 access-control-allow-origin
: *
을 넣어주는 편인데, 어떤 Origin
에서 요청하든 허용하겠다는 의미이다. (localhost:3000 이든,, IwillHackYou.com 같은 위험해 보이는 도메인이든..)
S3를 AWS 웹 콘솔로 배포 할 경우와, serverless
프레임워크를 이용할 경우 두가지 케이스에 대해서 설명한다.
우선 CORS를 활성화 하고자 하는 S3 버킷을 AWS 웹 콘솔을 통해 들어간다.
위 사진은 실제 이번 DND 4기 9조 사이드 프로젝트를 하면서 사용한 실제 S3 Static Resource 서버용 버킷이다.
위 사진에서 권한
부분을 클릭하여 들어간다.
밑으로 쭉 내리다 보면 CORS(Cross-origin 리소스 공유) 부분이 보인다. 이 부분을 편집을 눌러 다음과 같이 수정한다.
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"GET",
"HEAD"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": [
"x-amz-server-side-encryption",
"x-amz-request-id",
"x-amz-id-2"
],
"MaxAgeSeconds": 3000
}
]
위 설정은 모든 출처에 대해서 (*
) 리소스 공유를 허용한다는 내용의 설정이다.
service: dnd-4th-9-seeat-image
provider:
name: aws
stage: ${opt:stage, self:custom.defaultStage}
stackName: ${self:service}-${self:provider.stage}
imageBucket: ${self:service}-${self:provider.stage}-image-bucket
custom:
custom.defaultStage: dev
resources:
Description: "seeat image bucket stack"
Resources:
imageBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: ${self:provider.imageBucket}
AccessControl: Private
CorsConfiguration:
CorsRules:
- AllowedOrigins:
- '*'
AllowedHeaders:
- '*'
AllowedMethods:
- GET
- HEAD
MaxAge: 3000
imageBucketAllowPublicPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: ${self:provider.imageBucket}
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- "s3:DeleteObject"
- "s3:GetObject"
- "s3:ListBucket"
- "s3:PutObject"
#버킷에 객체를 삽입, 삭제, 리스트 가능한 권한 설정.
Resource:
- arn:aws:s3:::${self:provider.imageBucket}
- arn:aws:s3:::${self:provider.imageBucket}/*
#버킷내 모든 리소스를 누구나 접근 가능
Principal: "*" #모든 사용자가 접근 가능하도록 정의
위에서 CorsConfiguration:
부분을 잘 확인하여 동일하게 넣어서 Serverless
를 활용해 배포하면 된다.
Serverless 프레임워크에 대해서는 추후 자세히 포스팅 할 예정이니 이 부분이 이해가 가지 않는다면 넘어가도 좋습니다!
위 과정을 따라서 CORS 정책을 정의했다고 해서 html2canvas
나 혹은 lottie
와 같은 것들을 바로 사용 가능한 상태는 아니다.
S3버킷은 access-control-allow-origin
헤더를 특정 상황에서만 반환하는데..
클라이언트의 리소스 요청(request)에 Origin
헤더가 있어야만 access-control-allow-origin
헤더를 내려보내준다!
위 사진은 Origin 헤더 없이 요청한 응답의 결과. access-control-allow-origin 헤더가 응답에 포함되어 있지 않다.
위 사진은 Origin 헤더를 추가해서 요청한 응답의 결과. access-control-allow-origin 헤더가 응답에 포함되어있다.
따라서 대부분의 경우는 이슈가 없겠지만, html2canvas
등의 패키지를 클라이언트측에서 s3에 저장된 resource
를 재구성 할 경우 문제가 생긴다.
https://github.com/dnd-side-project/dnd-mentee-4th-9-repo/issues/182
위는 실제 사이드 프로젝트를 하면서 cors 에러가 해결되지 않아 처리했었던 이슈
S3에서 받아온 이미지를 canvas로 재구성 하면서 디스크에 캐싱된 이미지의 응답에 access-control-allow-origin이 없어서 발생한 에러 캡쳐 사진
이 부분을 해결하기 위해 많은 구글링을 하였는데, 구글
과 aws
는 서로 각각 본인들의 문제가 아니라고 주장한다고 하더라...
풀어서 설명하자면 받아온 이미지를 canvas
로 재구성하면서 이미 한번 내려받은 이미지를 재사용하는데, 이 이미지를 받을 당시의 응답에 access-control-allow-origin
이 없고, canvas
로 재구성하면서 cors
정책을 위반한다고 브라우저는 판단하고 cors
에러를 내뱉는다.
이 이슈를 해결하기 위해서는 서버측에서 access-control-allow-origin
헤더를 내려주어야 하는데 문제는 클라이언트 측에서 이미지 리소스를 받을 때 Origin
헤더를 담아서 보내지 않는다.
따라서 우리는 origin
헤더를 어떻게든 보내서 리소스 응답에 access-control-allow-origin
헤더를 받아와야 한다.
그래서 우리는 cloud front
를 활용해서 이러한 이슈를 해결하기로 했다.
cloud front
는 aws에서 제공하는 CDN서비스로, 사용자의 요청과 가장 가까운 Edge
컴퓨터가 응답을 해주는 서비스인데, 캐싱정책을 통해 원본 서버에 요청하지 않고 캐싱된 응답을 내려줘 서버의 부하를 낮추고, 응답 속도를 획기적으로 증가시키기도 하는 서비스이다.
cloud front
와 S3
를 함께 사용하게 되면 요청이 흐르는 과정은 다음과 같이 바뀐다.
사용자의 요청 -> cloud front -> S3 서버
이와 더불어 cloud front
에서 임의로 헤더를 설정해서 원본 서버로 내려줄 수 있는데, 그래서 나는 cloud front
에서 강제로 모든 요청에 대해서 Origin
헤더가 있는 것 처럼 S3
서버를 속이기로 했다.
Cloud Front에 대한 자세한 설명은 추후에 포스팅할 예정
위 사진의 맨 아래쪽을 주의해서 보자. Origin Custom Headers
라는 부분에 강제로 origin : "*"
이라는 헤더를 S3에 보내도록 해놓았다.
따라서 Cloud front
를 통해 S3에 요청을 하게 되면 access-control-allow-origin
헤더를 무조건 받을 수 있게 된다.
다만 여기서도 제약사항이 있었는데, 헤더의 이름을 Origin
으로 설정하려고 하면 AWS
에서 해당 헤더이름은 사용 불가능하다고 막는다. 따라서 소문자로 origin
으로 설정하여 보내고 있다.
cloud front
의 도메인(xxxx.cloudfront.net)으로 클라이언트에서 Origin
헤더 없이 요청해보고, access-control-allow-origin
이 받아지는지 테스트 해보았다.
결과는 성공!
따라서 이제 클라이언트 측에서 Origin
헤더를 굳이 담아서 보내지 않더라도, S3를 Cloud front
를 이용해 속여서(?) access-control-allow-origin
헤더를 받아 낼 수 있었다.
우리는 사이드 프로젝트에서 Domain
을 구입했기 때문에, cloud front
도메인을 https://resources.seeat-plant.com
과 연결하여 좀 더 이쁜 도메인으로 사용중이다.
resources.seeat-plant.com
으로 오는 요청을 cloud front
도메인으로 리다이렉션 하도록 Route53
설정을 변경하였다.
이로서 어디서 어떻게 요청하든, cloud front
가 임의로 origin
헤더를 만들어서 S3
에 넘겨주기 때문에 응답에는 무조건 access-control-allow-origin
이 생기게 된다.
이로서 S3
에 저장된 리소스를 활용해 html2canvas
혹은 lottie
를 재구성해서 사용해도 더이상 브라우저는 CORS
에러를 내뱉지 않을것이다.
사이드 프로젝트를 통해 정말 많은 이슈와 부딪혔고 이런 이슈들을 해결하기 위해 정말 많은 삽질을 했다. 하지만 이런 과정을 통해 정말 많이 배우고 성장했다.
너무 좋은 사이드프로젝트 팀원들과 함께했기 때문에 더 재밌게, 더 많이 성장한 느낌?
우리가 완성한 사이드 프로젝트는 https://www.seeat-plant.com
에서 확인 할 수 있다.
잘 읽었습니다! :)