Reco Everything you wanna value. 자세히보기

[IT|Programming]/HTML related

AWS S3 (Simple Storage Service) 공부하기 - Pre-signed URL

kipid 2025. 7. 6. 12:10
반응형
# AWS S3 (Simple Storage Service) 공부하기 - Pre-signed URL 이미지/동영상/파일 등을 업로드하고 다운로드 할 수 있게 도와주는 AWS S3 (Simple Storage Service) 를 배워봅시다. ## PH
  • 2025-07-06 : First posting.
## TOC ## S3 이미지/파일 업로드 예제 by edu-jason-kim 링크: .env file ```[.lang-js] AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... AWS_BUCKET_NAME=... ```/ aws-sdk 설치. ```[.lang-sh] npm i aws-sdk ```/ app.js file ```[.lang-js.scrollable] const express = require('express') const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3') const { getSignedUrl } = require('@aws-sdk/s3-request-presigner') const cors = require('cors') const app = express() app.use(cors()) app.use(express.json()) const port = 3000 const s3Client = new S3Client({ region: 'ap-northeast-2', credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, } }) app.post('/get-upload-url', async (req, res) => { const { fileName, fileType } = req.body const command = new PutObjectCommand({ Bucket: process.env.AWS_BUCKET_NAME, Key: `${Date.now()}_${fileName}`, ContentType: fileType, }) const uploadURL = await getSignedUrl(s3Client, command, { expiresIn: 300 }) res.json({ uploadURL }) }) app.listen(port, () => { console.log(`Example app listening on port ${port}`) }) ```/ upload.html file ```[.lang-html.scrollable] <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <input id="fileInput" type="file" /> <button id="uploadButton">업로드</button> <script> const input = document.getElementById("fileInput"); const button = document.getElementById("uploadButton"); button.addEventListener("click", async () => { const file = input.files[0]; // 업로드를 위한 signed url을 express server에서 부터 가지고 온다. const urlResponse = await fetch( "http://localhost:3000/get-upload-url", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ fileName: file.name, fileType: file.type, }), } ); const { uploadURL } = await urlResponse.json(); try { // signed url을 통해서 이미지를 업로드 한다. const uploadResponse = await fetch(uploadURL, { method: "PUT", headers: { "Content-Type": file.type }, body: file, }); input.value = ""; alert("성공적으로 업로드 되었습니다."); // 서버로 성공 req를 보낸다. } catch (error) { // 서버로 실패 req를 보낸다. } }); </script> </body> </html> ```/ ## 개인적인 정리 참고: AWS SDK Package 설치 ```[.lang-sh] npm init npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner npm install -D typescript @types/node npm i express npm i -D @types/express ```/ secrets.ts file (.gitignore 에 추가하고 .env 대신 이걸 쓰는것도 좋아 보임. 민감한 데이터는 공유하지 않고, 손수 복붙해주는 방식으로 보안 강화.) ```[.lang-ts] export const AWS_ACCESS_KEY_ID="..."; export const AWS_SECRET_ACCESS_KEY="..."; export const AWS_BUCKET_NAME="..."; ```/ s3-presigner.ts file ```[.lang-ts.scrollable] import { S3Client, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; // S3 클라이언트 초기화 (환경 변수 또는 IAM 역할에서 자격 증명 자동 로드) const s3Client = new S3Client({ region: process.env.AWS_REGION || "ap-northeast-2" }); // 기본 리전 설정 /** * S3에 파일을 업로드하기 위한 미리 서명된 URL을 생성합니다. * @param bucketName S3 버킷 이름 * @param objectKey 업로드될 파일의 S3 객체 키 (경로 포함) * @param expiresIn URL의 유효 시간 (초), 기본 1시간 (3600초) * @returns 미리 서명된 URL */ export async function createPresignedUrlForUpload( bucketName: string, objectKey: string, expiresIn: number = 3600 ): Promise<string> { const command = new PutObjectCommand({ Bucket: bucketName, Key: objectKey, // ContentType: 'image/jpeg', // 클라이언트에서 Content-Type을 지정해야 하므로 여기서는 생략 // ACL: 'public-read' // 필요시 접근 제어 목록 설정 }); try { const url = await getSignedUrl(s3Client, command, { expiresIn }); return url; } catch (error) { console.error("업로드용 미리 서명된 URL 생성 실패:", error); throw error; } } /** * S3에서 파일을 다운로드하기 위한 미리 서명된 URL을 생성합니다. * @param bucketName S3 버킷 이름 * @param objectKey 다운로드될 파일의 S3 객체 키 (경로 포함) * @param expiresIn URL의 유효 시간 (초), 기본 1시간 (3600초) * @returns 미리 서명된 URL */ export async function createPresignedUrlForDownload( bucketName: string, objectKey: string, expiresIn: number = 3600 ): Promise<string> { const command = new GetObjectCommand({ Bucket: bucketName, Key: objectKey, }); try { const url = await getSignedUrl(s3Client, command, { expiresIn }); return url; } catch (error) { console.error("다운로드용 미리 서명된 URL 생성 실패:", error); throw error; } } // 예시 사용 (테스트용) async function main() { const yourBucketName = "your-unique-s3-bucket-name"; // 실제 S3 버킷 이름으로 변경 const uploadFileKey = "uploads/my-document-ts.pdf"; const downloadFileKey = "downloads/existing-report-ts.csv"; // S3에 이미 존재하는 파일 try { const uploadUrl = await createPresignedUrlForUpload(yourBucketName, uploadFileKey); console.log(`업로드용 미리 서명된 URL: ${uploadUrl}`); console.log(`이 URL을 사용하여 '${uploadFileKey}' 파일을 S3에 업로드할 수 있습니다.`); const downloadUrl = await createPresignedUrlForDownload(yourBucketName, downloadFileKey); console.log(`다운로드용 미리 서명된 URL: ${downloadUrl}`); console.log(`이 URL을 사용하여 '${downloadFileKey}' 파일을 S3에서 다운로드할 수 있습니다.`); } catch (error) { console.error("오류 발생:", error); } } // 이 파일이 직접 실행될 때만 main 함수 호출 if (require.main === module) { main(); } ```/ server.ts file ```[.lang-ts.scrollable] import express from 'express'; import { createPresignedUrlForUpload, createPresignedUrlForDownload } from './s3-presigner'; // 위에서 작성한 파일 const app = express(); const port = 3000; app.use(express.json()); // JSON 요청 본문 파싱 // CORS 설정 (필요에 따라) app.use((req, res, next) => { res.setHeader('Access-Control-Allow-Origin', 'http://localhost:your-frontend-port'); // 프론트엔드 도메인 res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); next(); }); // 업로드용 URL 요청 엔드포인트 app.post('/generate-upload-url', async (req, res) => { const { fileName, contentType } = req.body; // 클라이언트로부터 파일 이름과 MIME 타입 받기 const bucketName = process.env.S3_BUCKET_NAME || "your-unique-s3-bucket-name"; // 환경 변수에서 버킷 이름 로드 const objectKey = `uploads/${fileName}`; // S3에 저장될 경로와 파일 이름 if (!fileName || !contentType) { return res.status(400).json({ error: 'fileName and contentType are required.' }); } try { const uploadUrl = await createPresignedUrlForUpload(bucketName, objectKey); res.json({ uploadUrl, objectKey }); } catch (error) { console.error('업로드 URL 생성 API 오류:', error); res.status(500).json({ error: 'Failed to generate upload URL.' }); } }); // 다운로드용 URL 요청 엔드포인트 app.get('/generate-download-url/:objectKey', async (req, res) => { const objectKey = req.params.objectKey; const bucketName = process.env.S3_BUCKET_NAME || "your-unique-s3-bucket-name"; try { const downloadUrl = await createPresignedUrlForDownload(bucketName, objectKey); res.json({ downloadUrl }); } catch (error) { console.error('다운로드 URL 생성 API 오류:', error); res.status(500).json({ error: 'Failed to generate download URL.' }); } }); app.listen(port, () => { console.log(`서버가 http://localhost:${port} 에서 실행 중입니다.`); }); ```/ ## RRA
  1. S3 이미지/파일 업로드 예제 (GitHub)
  • 미리 서명된 URL을 통해 객체 다운로드 및 업로드
  • aws - /userguide/ShareObjectPreSignedURL
  • aws - /userguide/PresignedUrlUploadObject
  • aws - /API/s3_example_s3_Scenario_PresignedUrl_section
  • aws - /sdk-for-javascript/v3/developer-guide/javascript_s3_code_examples
  • 반응형