AWS-SDK 로 Presigned URL 을 통해 S3에 파일 업로드 (Typescript)

1. S3 버킷 생성

생략

2. 사용자 추가

대상 버킷에 액세스할수있는 권한을 가진 사용자 및 그룹을 생성하여 Access key 와 Secret acess key 를 얻는다.

3. 구현

3.1 패키지 설치

aws-sdk 3 버전 기준으로 aws-sdk/client-s3, aws-sdk/s3-request-presigner 설치

yarn add @aws-sdk/client-s3 @aws-sdk/s3-request-presigner

3.2 S3 클라이언트

버킷이 생성되어 있는 Region과 access key, secret access key 를 파라미터로 S3client 생성한다.

// Create service client module using ES6 syntax.
import { S3Client } from "@aws-sdk/client-s3";
// Set the AWS Region.
const REGION = "<your_region>"; //e.g. "us-east-1"
const accKey = "<access_key>";
const secretAccKey = "<secret_access_key>";
// Create an Amazon S3 service client object.
const s3Client = new S3Client({
  region: REGION,
  credentials: {
    accessKeyId: accKey,
    secretAccessKey: secretAccKey,
  },
});
export default s3Client;

3.3 Presigned URL 생성

Presigned URL을 생성해주는 API 코드 (nextjs)

import { NextApiRequest, NextApiResponse } from "next";
import { PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

// 3.2에서 작성한 S3 client
import s3Client from "lib/s3Client";

const S3GetUploadUrl = async (req: NextApiRequest, res: NextApiResponse) => {
  try {
    // req에서 파일이름 가져오기
    let { fileName } = req.body;

    // url 만료 시간
    const exp = parseInt(process.env.S3_PRESIGNED_URL_EXPIRATION_SECONDS) || 70;

    /**
     * Bucket: 생성한 버킷 이름
     * Key: 버킷에 저장될 파일 이름 및 경로 (ex: dir1/dir2/filename)
     */
    const bucketParams = {
      Bucket: process.env.S3_BUCKET_NAME,
      Key: `test/${Math.ceil(Math.random() * 10 ** 10)}/${fileName}`,
    };

    // PutObjectCommand는 파일을 버킷에 Put 하기 위한 객체
    const putObjectCommand = new PutObjectCommand(bucketParams);
    const url = await getSignedUrl(s3Client, putObjectCommand, {
      expiresIn: exp,
    });

    console.log(`Generated url: ${url}`);

    // 요청한 클라이언트에 생성된 URL 전송
    res.json({ url });
  } catch (err) {
    console.log(err);
  }
};

export default S3GetUploadUrl;

3.4 URL 요청 및 업로드

const uploadFile = async (
  file: File,
  onUploadProgress?: (e: ProgressEvent) => void
) => {
  try {
    // Presigned URL 요청
    const url = await axios
      .post(
        `<Presigned_URL_API_URL>`,
        {
          fileName: file.name,
        },
        {
          headers: {
            /* Authorization */
          },
        }
      )
      .then((res) => {
        return res.data.url;
      });

    // Presigned URL로 파일 업로드
    await axios.put(url, file, {
      headers: {
        "Content-Type": file.type,
        "Access-Control-Allow-Origin": "*",
      },
      onUploadProgress,
    });
  } catch (err) {
    console.log(err);
  }
};