Skip to content

pre signed url로 파일 업로드하기

김서진 edited this page Dec 2, 2021 · 4 revisions

image

개요

1. 배경

11월 19일 금요일 여느 때와 같이 우리는 데모 발표를 하고 있었다.

Object Storage를 프론트에서 적용한 내용을 공유했다.

발표를 듣고 성기혁 캠퍼님께서 의견을 주셨다.

클라이언트면 브라우저에서 Object Storage 접근하는건가요?
그러면 secret_key가 브라우저에 공개되서 위험할것 같아요
react build하면 코드가 만들어져서 위험할것 같아요
그래서 보통은 백엔드로 요청을 보내고
백엔드에서만 업로드 허용한 상태로 object storage에 업로드할거에요

그리고 김동환 캠퍼님께서 의견을 주셨다

s3 signed url 기능을 이용해보시면 좋을거같아요

2. 목표

앗! 지금처럼 구현한 게 보안상 문제가 되다니!

문제를 해결하기 위해 S3를 백엔드로 보내기로 결정했다.

우리의 목적은 서버에서 업로드하지 않는 것이어서

putObject 요청을 백엔드에서 하지 않고 우리의 목적에 맞게 signed url을 적용해보기로 했다.

과정

1. 단계 분석하기

검색을 통해 pre-signed url로 이미지를 업로드하는 블로그 글을 찾았다. 이 글을 참고해서 과정을 진행했다.

그림을 그려서 단계를 분석하고 순서대로 진행해보기로 했다.

  1. 클라이언트가 서버에 이미지 업로드 요청을 보낸다
  2. 서버가 S3에 요청을 해서 생성한 pre-signed url을 받는다.
  3. 서버는 pre-signed url을 프론트에 보내준다.
  4. 프론트가 해당 pre-signed urlput 요청을 보낸다.

도전!!!

2. 서버에 이미지 업로드 요청 보내기

이 과정은 이전에 진행하긴 했는데 설명을 위해서 코드를 추가했다. 파일 이름을 넣어서 getPresignedUrl을 호출한다.

const target = e.target as HTMLInputElement;
const file: File = (target.files as FileList)[0];
if (!file.type.match('image/jpeg|image/png')) return;
const uploadName = `${new Date().toLocaleString()}-${file.name}`;
const presignedUrl = await getPresignedUrl(uploadName);

getPresignedUrl은 업로드할 파일의 이름을 body에 담아서 서버에 요청을 보낸다.

const getPresignedUrl = async (uploadName: string) => {
  try {
    const response = await fetch(/api/user/presignedurl, postFetchOptions({ uploadName }));
    const url = await response.json();
    return url;
  } catch (error) {
    console.log(error);
  }
};

3. S3에 요청을 해서 pre-signed url 생성하기

/api/user/presignedurlpost 요청을 보내면 getPresignedUrl를 실행한다. getPresignedUrlgetPresignUrl를 호출해서 pre-signed url을 받아온다.

const getPresignedUrl = async (req: Request, res: Response, next: NextFunction) => {
  try {
    const uploadName = req.body.uploadName;
    const url = await getPresignUrl(uploadName);
    return res.status(200).json({ url });
  } catch (error) {
    next(error);
  }
};

getPresignUrl은 S3의 getSignedUrlPromise 메서드로 pre-signed url을 요청한다.

우리는 이미지를 업로드해야하므로 첫번째 인자에 putObject를 넣어주고

params에는 Bucket, Key, Expires를 작성한다.

const getPresignUrl = async (fileName) => {
  const params = {
    Bucket: bucketName,
    Key: fileName,
    Expires: 60 * 60 * 3,
  };

  const signedUrlPut = await S3.getSignedUrlPromise('putObject', params);
  return signedUrlPut;
};

4. 프론트에서 pre-signed url로 put 요청 보내기

이제 프론트에서 uploadFileWithPresignedUrl 함수를 호출한다.

const uploadedFile = await uploadFileWithPresignedUrl(presignedUrl.url, file);

uploadFileWithPresignedUrl 함수는 pre-signed urlfile을 받아서 pre-signed urlput 요청을 보낸다.

export const uploadFileWithPresignedUrl = async (url: string, file: File) => {
  try {
    const response = await fetch(
      new Request(url, {
        method: 'PUT',
        credentials: 'include',
        body: file,
      }),
    );
    return response.url;
  } catch (err) {
    console.log(err);
  }
};

여기서 문제가 생겼다. cors 에러가 뜬다.

Pasted Graphic 5

cors 설정을 해줘야되나? 이미 지난 번에 했는데....ㅠㅠㅠ

credentials: 'include' 옵션을 추가해보고

CORS 설정에 HEAD도 추가해봤는데 해결이 되지 않았다.

이미지가 업로드되는 경우와 되지 않는 경우의 헤더를 비교해보았다. 요청이 거절되는 것 같았다.

Scheme https ® Content-Length 14954 Scheme https ® Sec-Fetch-Site cross-site

pre-signed url을 확인해보니 SignatureDoesNotMatch라는 오류가 나타났다.

image

5. signatureVersion 설정하기

SignatureDoesNotMatch를 stack overflow에 검색해서 해결방법이 담긴 글을 찾았다!

S3를 생성할 때 signatureVersion: 'v4'를 적용해야 된다고 한다.

오 이제 보내진다!!! 파일을 불러오면 끝!

const uploadedURL = 'https://kr.object.ncloudstorage.com/duxcord/' + uploadName;

잉 근데 파일을 불러오지를 못한다 ㅠ

image

뭐가 잘못된걸까 계속 찾아봤다 ㅠㅠ

버킷을 확인해보니까 권한이 없었다!! 파일이 공개 안함 설정이 되어 있었다

pre-signed url로 업로드를 하면 자동으로 공개 안함 설정이 되는 것 같았다.

Pasted Graphic 12

6. 헤더에 ACL 속성 추가하기

같은 문제를 가지고 있는 글을 발견해서 코드를 참고해서 헤더에 ACL을 추가해봤다.

pre-signed url을 요청할 때 params에 ACL: 'public-read'을 추가하고

프론트에서 스토리지에 put 요청을 보낼 때 헤더에 'x-amz-acl': 'public-read'를 추가해줬다.

image

짜란!! 업로드 된다!! 성공한 결과!

image image

보안을 버리지 않을 수 있게 되어서 다행이다 ㅎㅎ

참고자료

AWS S3 presignedURL을 이용해서 image Upload 하기: https://velog.io/@seeh_h/AWS-S3-presignedURL%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%B4%EC%84%9C-image-Upload-%ED%95%98%EA%B8%B0-qvqo81gk

Pre-signed url 을 이용한 S3 업로드: https://atercatus.github.io/wedev/2019-11-19-aws-lambda

stack overflow - SignatureDoesNotMatch: https://stackoverflow.com/questions/30518899/amazon-s3-how-to-fix-the-request-signature-we-calculated-does-not-match-the-s

ACL: https://www.digitalocean.com/community/questions/upload-aws-s3-getsignedurl-with-correct-permissions-and-content-type

Clone this wiki locally