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 });
  const url = await getSignedUrl(s3, command, {
    expiresIn: 300,  // 5分
  });
  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,
  },
}));

ポイント

  • Presigned URLは署名情報がURLに含まれているため、AWS資格情報なしで直接ダウンロードできます。有効期限が過ぎると403が返されます。
  • DynamoDB TTLを設定すると、期限切れのトークンが自動削除されます。クーロンジョブでのクリーンアップは不要です。
  • expiresInはURL自体の有効時間、DynamoDB TTLはトークンの有効時間です。これらを分離して管理するとより柔軟です。