S3 Presigned URL로 일회용 다운로드 링크 만들기

문제

파일을 다운로드할 수 있는 링크를 제공하고 싶은데, 누구나 무제한 다운로드 가능하면 안 된다. 시간 제한 + 횟수 제한이 필요하다.

해결

import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const s3 = new S3Client({ region: 'ap-northeast-2' });

async function generateDownloadUrl(bucket: string, key: string) {
  const command = new GetObjectCommand({ Bucket: bucket, Key: key });

  // 5분간 유효한 presigned URL 생성
  const url = await getSignedUrl(s3, command, {
    expiresIn: 300,  // 초 단위
  });

  return url;
}

// DynamoDB에 토큰 저장 (1시간 TTL, 최대 1회 다운로드)
const token = crypto.randomUUID();
await docClient.send(new PutCommand({
  TableName: 'download-tokens',
  Item: {
    PK: `TOKEN#${token}`,
    downloadCount: 0,
    maxDownloads: 1,
    ttl: Math.floor(Date.now() / 1000) + 3600,  // 1시간 후 자동 삭제
  },
}));

핵심 포인트

  • Presigned URL은 서명 정보가 URL에 포함되어 있어서 AWS 자격 증명 없이 직접 다운로드 가능하다. 하지만 만료 시간이 지나면 403이 반환된다.
  • DynamoDB TTL을 설정하면 만료된 토큰이 자동 삭제된다. 크론잡으로 정리할 필요가 없다.
  • expiresIn은 URL 자체의 유효 시간이고, DynamoDB TTL은 토큰의 유효 시간이다. 둘을 분리해서 관리하면 더 유연하다.