-
Notifications
You must be signed in to change notification settings - Fork 7
pre signed url로 파일 업로드하기
11월 19일 금요일 여느 때와 같이 우리는 데모 발표를 하고 있었다.
Object Storage를 프론트에서 적용한 내용을 공유했다.
발표를 듣고 성기혁 캠퍼님께서 의견을 주셨다.
클라이언트면 브라우저에서 Object Storage 접근하는건가요?
그러면 secret_key가 브라우저에 공개되서 위험할것 같아요
react build하면 코드가 만들어져서 위험할것 같아요
그래서 보통은 백엔드로 요청을 보내고
백엔드에서만 업로드 허용한 상태로 object storage에 업로드할거에요
그리고 김동환 캠퍼님께서 의견을 주셨다
s3 signed url 기능을 이용해보시면 좋을거같아요
앗! 지금처럼 구현한 게 보안상 문제가 되다니!
문제를 해결하기 위해 S3를 백엔드로 보내기로 결정했다.
우리의 목적은 서버에서 업로드하지 않는 것이어서
putObject
요청을 백엔드에서 하지 않고 우리의 목적에 맞게 signed url
을 적용해보기로 했다.
검색을 통해 pre-signed url
로 이미지를 업로드하는 블로그 글을 찾았다.
이 글을 참고해서 과정을 진행했다.
그림을 그려서 단계를 분석하고 순서대로 진행해보기로 했다.
- 클라이언트가 서버에 이미지 업로드 요청을 보낸다
- 서버가 S3에 요청을 해서 생성한
pre-signed url
을 받는다. - 서버는
pre-signed url
을 프론트에 보내준다. - 프론트가 해당
pre-signed url
로put
요청을 보낸다.
도전!!!
이 과정은 이전에 진행하긴 했는데 설명을 위해서 코드를 추가했다.
파일 이름을 넣어서 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);
}
};
/api/user/presignedurl
에 post
요청을 보내면 getPresignedUrl
를 실행한다.
getPresignedUrl
는 getPresignUrl
를 호출해서 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;
};
이제 프론트에서 uploadFileWithPresignedUrl
함수를 호출한다.
const uploadedFile = await uploadFileWithPresignedUrl(presignedUrl.url, file);
uploadFileWithPresignedUrl
함수는 pre-signed url
과 file
을 받아서 pre-signed url
에 put
요청을 보낸다.
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 에러가 뜬다.
cors 설정을 해줘야되나? 이미 지난 번에 했는데....ㅠㅠㅠ
credentials: 'include'
옵션을 추가해보고
CORS 설정에 HEAD도 추가해봤는데 해결이 되지 않았다.
이미지가 업로드되는 경우와 되지 않는 경우의 헤더를 비교해보았다. 요청이 거절되는 것 같았다.
pre-signed url을 확인해보니 SignatureDoesNotMatch
라는 오류가 나타났다.
SignatureDoesNotMatch
를 stack overflow에 검색해서 해결방법이 담긴 글을 찾았다!
S3를 생성할 때 signatureVersion: 'v4'
를 적용해야 된다고 한다.
오 이제 보내진다!!! 파일을 불러오면 끝!
const uploadedURL = 'https://kr.object.ncloudstorage.com/duxcord/' + uploadName;
잉 근데 파일을 불러오지를 못한다 ㅠ
뭐가 잘못된걸까 계속 찾아봤다 ㅠㅠ
버킷을 확인해보니까 권한이 없었다!! 파일이 공개 안함 설정이 되어 있었다
pre-signed url로 업로드를 하면 자동으로 공개 안함 설정이 되는 것 같았다.
같은 문제를 가지고 있는 글을 발견해서 코드를 참고해서 헤더에 ACL을 추가해봤다.
pre-signed url을 요청할 때 params에 ACL: 'public-read'
을 추가하고
프론트에서 스토리지에 put 요청을 보낼 때 헤더에 'x-amz-acl': 'public-read'
를 추가해줬다.
짜란!! 업로드 된다!! 성공한 결과!
보안을 버리지 않을 수 있게 되어서 다행이다 ㅎㅎ
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