Pre-signed URL이란 무엇인지에 대해 다룹니다.

개념

Pre-signed URL은 S3에 저장된 객체에 대해 제한된 시간 동안 액세스 권한을 제공하는 특수한 URL입니다.

이 URL 을 통해 사용자 또는 클라이언트가 특정 파일을 읽거나 업로드할 수 있도록 허용하므로써 보안을 유지할 수 있습니다.

동작 원리

  1. 서버에서 Pre-signed URL 생성
  2. 클라이언트에서 URL 사용
  3. 만료 시간 이후 URL 사용 불가

구현

면시 프로젝트에는 전통적인 파일업로드(서버에서 업로드)와 Pre-signed URL 방식이 모두 구현되어있습니다.

개념자체는 생소할 수 있으나, @aws-sdk/s3-request-presigner 라이브러리를 이용하면 쉽게 구현가능합니다.

import { S3Client, GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { randomUUID } from "crypto";
import { Files } from "openai/resources";
import { Member } from "src/interfaces/member.interface";

@Injectable()
export class S3Service {
  private s3: S3Client;

  constructor(private readonly configService: ConfigService) {
    this.s3 = new S3Client({
      region: this.getRegion(),
      credentials: {
        accessKeyId: this.configService.get<string>('AWS_ACCESS_KEY_ID') as string,
        secretAccessKey: this.configService.get<string>('AWS_SECRET_ACCESS_KEY') as string,
      },
    });
  }

  /**
   * 다운로드 가능한 Pre-signed URL 반환
   */
  async createDownloadUrl(key: string): Promise<Files.PresignedResponse> {
    const command = new GetObjectCommand({
      Bucket: this.getBucket(),
      Key: key,
    });

    const url = await getSignedUrl(this.s3, command, { expiresIn: 3600 });
    return { url };
  }

  /**
   * 업로드 가능한 Pre-signed URL 반환
   */
  async createUploadUrl(memberId: Member['id']): Promise<Files.PresignedResponse> {
    const key = `${memberId}/${randomUUID()}`;

    const command = new PutObjectCommand({
      Bucket: this.getBucket(),
      Key: key,
    });

    const url = await getSignedUrl(this.s3, command, { expiresIn: 3600 });
    return { url };
  }