[AWS] S3와 이미지 파일 다루기
S3를 기존에 사용해왔기에 AWS 자원에 연결하고 관리하는 방법보다는 이미지 파일을 업로드 및 다운로드하면서 발생한 여러 문제에 대한 해결방법을 목적에 둔 글입니다 🙂
S3에 파일 업로드하기
아래 코드는 클라이언트에서 전송한 파일을 읽기가 가능한 스트림으로 만들어 S3에 업로드하는 간단한 방식입니다.
async uploadImage(file: any): Promise<string> {
const fileKey = `...`;
const s3Params = {
Bucket: process.env.BUCKET_NAME || AwsInfo.DEFAULT_BUCKET_NAME,
Key: fileKey,
Body: file.createReadStream(),
ContentType: file.mimeType,
ContentEncoding: file.encoding,
};
return this.putS3(s3Params);
}
/**
* AWS S3에 put 하고 해당 객체의 URL을 반환하는 메소드 입니다.
*
* @param putObjectRequest
* @private
*/
async putS3(putObjectRequest: awsS3.Types.PutObjectRequest): Promise<string> {
return this.s3
.upload(putObjectRequest)
.promise()
.then((data) => data.Location);
}
테스트할 때는 전혀 문제가 없는 것처럼 보였지만,
실제 클라이언트에서 파일을 전송해보니 S3에 업로드된 파일의 Content-Type
이 application/octet-stream
으로 설정되어 파일이 출력되지 않는 문제를 발견했습니다.
서버에서는 GraphQL 실행기를 통해 파일이 전송되어 자동으로 이미지 타입이 설정되어 들어왔는데, 클라이언트에서는 그렇지 않아서 MIME-TYPE을 출력해보니 image/png
인것도 있고 application/octet-stream
인 것도 있었습니다.
이렇게 파일의 MIME-TYPE(마임 타입으로 읽는다고 합니다🙂)이 제대로 설정되지 않았기에, 서버에서 파일의 확장자 명을 통해 타입을 지정해 업로드 하도록 로직을 수정했습니다.
export const getContentType = (fileName: string): string => {
const extensions = {...};
const contentType = extensions[path.extname(fileName).replace('.', '')]; // ⬅️
if (!contentType) {
// throw error ...
// 사전에 협의된 파일 형식이 아닌 경우, 에러를 반환합니다.
}
return contentType;
};
async uploadImage(file: any): Promise<string> {
const fileKey = `...`;
const s3Params = {
Bucket: process.env.BUCKET_NAME || AwsInfo.DEFAULT_BUCKET_NAME,
Key: fileKey,
Body: file.createReadStream(),
ContentType: this.getContentType(file.filename), // ⬅️ 바뀐 부분
ContentEncoding: file.encoding,
};
return this.putS3(s3Params);
}
웹에서 파일을 주고받는 작업을 할 때, 항상 문제가 많이 발생하는 것 같습니다.
엑셀 파일을 처리하는 방식은 이미지와 또 다른 접근이 필요했고, 클라이언트까지 잘 알고있어야하는 등... 이런 문제들을 정리한 글을 조만간 업로드해보겠습니다 💪
S3 파일에 접근하기
객체 URL 활용하기
S3에 업로드된 객체의 속성을 살펴보면 아래와 같습니다.
객체 URL
을 통해 바로 클라이언트에서 인라인하여 화면에 출력할 수 있습니다.
하지만 대부분의 S3 버킷이 퍼블릭 엑세스에 제한되어 있기에 정책을 설정할 필요가 있습니다.
권한이 없는 경우의 응답은 아래와 같습니다.
<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>...</RequestId>
<HostId>...</HostId>
</Error>
저는 운영환경(local
, dev
, staging
, prod
) 별로 버킷을 별도로 관리하고 있기에 조금씩 다른 버킷 정책을 적용할 것 입니다.
로컬, 개발, 스테이징 환경의 경우 개발 및 테스트 용도로 활용되기에
접근 권한을 모두 허용하도록 설정했습니다.
{
"Sid": "Statement1",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject", // ⬅️
"Resource": [
"arn:aws:s3:::.../local/*",
"arn:aws:s3:::.../dev/*",
"arn:aws:s3:::.../staging/*"
]
}
운영의 경우, 보안이 필요한 민감한 데이터가 존재하기에
HTTP 요청 헤더를 분석해서 특정 도메인에서 요청한 경우에만 접근할 수 있도록 설정했습니다.
{
"Sid": "Statement1",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::.../prod/*",
"Condition": {
"StringLike": {
"aws:Referer": [운영 도메인 주소] // ⬅️
}
}
}
referer
란, 특정 요청을 보낸 경우 헤더에 포함된 속성 중 하나입니다.
예를 들어 아래는 네이버에서 특정 이미지를 요청하는 헤더 중 일부입니다.
referer
를 보면 네이버 도메인(https://www.naver.com/
) 값을 표현하고 있습니다. 즉, 네이버 도메인에서 이미지를 불러오는 요청을 한 것입니다.
이를 바탕으로 운영 도메인에서 S3의 운영 버킷에 있는 객체에 접근할 수 있도록 정책을 설정한 것입니다.
presignedURL
활용하기
위 방식 이전에 사용했던 해결방안으로 presignedURL
이 있습니다.
presignedURL
이란, 말 그대로 미리 서명된 URL 입니다. 권한이 있는 사용자에 의해 접근가능한 URL을 생성하여 공유하는 방식입니다.
async getPresignedUrl(fileKey: string): Promise<string> {
const s3Params = {
Bucket: process.env.BUCKET_NAME || AwsInfo.DEFAULT_BUCKET_NAME,
Key: fileKey,
// Expires: 120
};
return this.s3.getSignedUrl('getObject', s3Params);
}
this.s3.getSignedUrl('getObject', s3Params)
이 코드로 퍼블릭 엑세스가 아닌 버킷에 접근할 수 있는 이유는,
현재 제 계정이 접근 권한이 있는 사용자임으로 서명된 URL을 생성할 수 있는 것입니다.
presignedURL
의 세션이 만료되는 기본 값은 15분이며 Expires
옵션으로 조절할 수 있습니다.
이 방식을 활용하지 않게 된 이유는 DB 저장방식에 있습니다.
이미지 파일에 대한 정보를 DB에 저장하고 있는데, presignedURL
을 사용할 경우 유효시간이 있기에 값에 변동이 있어 주기적으로 갱신해주는 데 비용이 들게 됩니다.
그래서 파일을 업로드함과 동시에 객체 URL을 DB에 저장하여 해당 정보를 응답할 때, 파일 정보를 함께 보내줄 수 있도록 구현했습니다.
Author And Source
이 문제에 관하여([AWS] S3와 이미지 파일 다루기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@haneulhaneul/AWS-S3와-이미지-파일-다루기저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)