최근에 진행한 프로젝트에서 사진을 POST해야 하는 곳이 있었다.
바로 서버에 POST하는 방법만 사용하다가 AWS S3 bucket에 업로드하는 방법을 사용하게 되었다.
Presigned url을 활용해 AWS S3 bucket에 업로드하는 방식이다.
AWS에서 Presigned url는 "미리 서명된 url"이라고 번역되어있다.
기본적으로 모든 Amazon S3 객체는 프라이빗이며 객체 소유자만 액세스할 수 있는 권한이 있다. 하지만 presigned url을 사용하면 미리 서명된 URL을 생성하여 다른 사람과 객체를 공유할 수 있다!
다른 사람이 Amazon S3 버킷에 객체를 업로드하도록 허용하는 것이다.
왜 바로 서버에 파일을 업로드 하지 않고 이 Presigned url을 사용하는 것일까?
원래대로라면
브라우저 -> 서버 -> S3
이런 순서로 s3에 이미지를 업로드했어야 한다.
이 과정은 자원이 많이 소모되고 비효율적이다.
저장하지도 않을 무거운 이미지 파일을 서버로 전송하는 과정이 포함되어 있기 때문이다 😱
presigned url을 활용해 s3에 이미지를 올리는 방법은 대략 2가지 정도가 있다.
서버를 배제한 채 presigned URL 발급 로직 및 업로드 로직을 브라우저에서 전부 진행하는 방법
🚨 하지만 ... presigned url 을 발급 받는 과정에서 S3에 대한 접근 인증 권한을 가진 credential이 노출되어 누군가 가져간다면 .. 누구나 나의 S3 버켓에 접근할 수 있게 된다.. 배포했는데 누군가 credential을 탈취해 이미지를 모두 지운다면 ..? 😱
백엔드에서 S3에 대한 접근 권한 인증 및 presigned url을 발급 받아 브라우저에 전달하고, 브라우저에서 이를 사용해 직접 이미지를 업로드 하는 방법
2번 방식을 채택하는 것이 더 보안적이기에 2번 방식을 활용했다.

흐름을 이해해보자
- 클라이언트가 서버에 파일 업로드를 위한 pre-signed URL을 요청
- 서버는 이 요청을 S3에 전달
- S3는 새로운 pre-signed URL을 서버에 반환
- 서버는 그 pre-signed URL을 클라이언트에 반환
- 사용자가 파일을 선택하면, 클라이언트는 선택한 파일을 pre-signed URL을 사용하여 S3에 직접 업로드
- 클라이언트는 업로드가 완료되었음을 서버에 다시 알림.
이때 서버는 최소한의 상태만 저장하기 위해 pre-signed URL을 저장하지 않고, 대신 클라이언트가 이 요청에 Step 1에서 받은 pre-signed URL을 포함하게 함. 서버는 해당 pre-signed URL을 avatarUrl 필드에 기록하고, 이를 데이터베이스에 저장
pre-signed URL을 요청

새로운 파일 객체 생성

presigned url로 파일 업로드

성공 시 S3 파일 URL 생성

서버에 업로드 완료 알리기

서버에 업로드 완료 알림 전송

getPresignedUrl(file.originalName)을 통해 백엔드에서 Presigned URL을 발급받고 있고,
발급받은 URL(presignedUrl)을 사용해, 브라우저에서 axios.put(decodedPresignedUrl, newFile, ...)로 S3로 직접 업로드하고 있다
관리자 공지 작성 페이지에서 이미지 업로드 부분이 있었고, 이 함수를 활용해 이미지 업로드를 했는데, 코드를 봐보자.
const handleAdminPost = async () => {
const check = confirm("작성하시겠습니까?");
if (check) {
try {
let imageUrl = null;
if (uploadedFiles.length > 0) {
setUploadMessage("공지 등록중");
const uploadedFileUrls = await uploadFilesToS3(
uploadedFiles,
setUploadMessage
);
imageUrl = uploadedFileUrls.length > 0 ? uploadedFileUrls[0] : null;
}
이런식으로 사용할 수 있다!