๐Ÿš€ AWS S3 ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ

Kang Junhyeokยท2025๋…„ 8์›” 2์ผ

๐Ÿš€ AWS S3 ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๊ตฌํ˜„ ๊ฐ€์ด๋“œ

์ผ๋ฐ˜์ ์œผ๋กœ ๋งŽ์€ ํ”„๋กœ์ ํŠธ๋“ค์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ํ•˜๋Š” ๊ธฐ๋Šฅ๋“ค์ด ๋งŽ์ด ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค.
๊ทธ๋ž˜์„œ AWS ๊ธฐ๋ฐ˜์œผ๋กœ S3 + CloudFront + Presigned URL ์กฐํ•ฉ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋น ๋ฅด๊ณ 
์•ˆ์ „ํ•˜๋ฉฐ ๋น„์šฉ ํšจ์œจ์ ์ธ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


1๏ธโƒฃ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์•„ํ‚คํ…์ฒ˜

ํ๋ฆ„

  1. ํด๋ผ์ด์–ธํŠธ โ†’ ์„œ๋ฒ„: ์—…๋กœ๋“œ ์š”์ฒญ
  2. ์„œ๋ฒ„ โ†’ AWS: Presigned URL ์ƒ์„ฑ
  3. ์„œ๋ฒ„ โ†’ ํด๋ผ์ด์–ธํŠธ: Presigned URL ์ „๋‹ฌ
  4. ํด๋ผ์ด์–ธํŠธ โ†’ S3: ์ด๋ฏธ์ง€ ์ง์ ‘ ์—…๋กœ๋“œ
  5. ์„œ๋ฒ„ โ†’ DB: ์—…๋กœ๋“œ ์ •๋ณด(๊ฒฝ๋กœ, ํŒŒ์ผ๋ช…, ์œ ์ €) ์ €์žฅ
  6. ์กฐํšŒ ์‹œ: CloudFront URL ํ†ตํ•ด ์ด๋ฏธ์ง€ ์ ‘๊ทผ

โœ… ์žฅ์ : ์„œ๋ฒ„ ๋ถ€ํ•˜ ์ตœ์†Œํ™” + ๋น ๋ฅธ CDN ์กฐํšŒ + ๋ณด์•ˆ ๊ฐ•ํ™”


2๏ธโƒฃ Spring Boot ๊ตฌํ˜„

๐Ÿ”น Presigned URL ์ƒ์„ฑ

@Service
@RequiredArgsConstructor
public class S3Service {
    private final AmazonS3 amazonS3;
    private final String bucketName = "demo-images";

    public String generatePresignedUrl(String fileName) {
        Date expiration = new Date(System.currentTimeMillis() + 1000 * 60 * 5); // 5๋ถ„ ์œ ํšจ
        GeneratePresignedUrlRequest request =
            new GeneratePresignedUrlRequest(bucketName, fileName)
            .withMethod(HttpMethod.PUT)
            .withExpiration(expiration);
        return amazonS3.generatePresignedUrl(request).toString();
    }
}

๐Ÿ”น ํด๋ผ์ด์–ธํŠธ ์—…๋กœ๋“œ (React Native ์˜ˆ์‹œ)

await axios.put(presignedUrl, file, {
  headers: { "Content-Type": file.type }
});

3๏ธโƒฃ CloudFront + S3 ๊ตฌ์กฐ

  • S3 (Private): ์ด๋ฏธ์ง€ ์ €์žฅ
  • CloudFront: ์ „ ์„ธ๊ณ„ CDN ์บ์‹ฑ
  • OAC(Origin Access Control): S3 ์ง์ ‘ ์ ‘๊ทผ ์ฐจ๋‹จ
  • Signed URL(Optional): ๋ฏผ๊ฐํ•œ ์ด๋ฏธ์ง€ ์ ‘๊ทผ ์ œ์–ด
    ์—…๋กœ๋“œ์ค‘..

(์˜ˆ์‹œ ๋‹ค์ด์–ด๊ทธ๋žจ: S3 + CloudFront + Presigned URL ๊ตฌ์กฐ)


4๏ธโƒฃ ๋ณด์•ˆ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

์—…๋กœ๋“œ ์„œ๋น„์Šค๋Š” XSSยทํŒŒ์ผ ์œ„์žฅยท๋Œ€์šฉ๋Ÿ‰ ์—…๋กœ๋“œ์— ์ทจ์•ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์•„๋ž˜ ๋ณด์•ˆ ์ „๋žต์„ ์ ์šฉํ•˜๋ฉด ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.

  • ํ—ˆ์šฉ ํ™•์žฅ์ž๋งŒ ์—…๋กœ๋“œ (jpg, png, webp)
  • MIME ํƒ€์ž… ๊ฒ€์ฆ (image/ ๋กœ ์‹œ์ž‘ ์—ฌ๋ถ€)
  • ํŒŒ์ผ ํฌ๊ธฐ ์ œํ•œ (1~2MB)
  • UUID ๊ธฐ๋ฐ˜ ํŒŒ์ผ๋ช… (์‚ฌ์šฉ์ž ์ž…๋ ฅ ์ด๋ฆ„ ์‚ฌ์šฉ โŒ)
  • S3 Private + Presigned URL ์‚ฌ์šฉ
  • CloudFront OAC ์„ค์ •์œผ๋กœ ์ง์ ‘ ์ ‘๊ทผ ์ฐจ๋‹จ
  • ํ•„์š” ์‹œ Signed URL or JWT ์ธ์ฆ
  • ์—…๋กœ๋“œ ํ›„ Lambda๋กœ ์ด๋ฏธ์ง€ ํฌ๋งท ๊ฒ€์ฆ

5๏ธโƒฃ ๋น„์šฉ ์ตœ์ ํ™”

  • ํ”„๋ฆฌํ‹ฐ์–ด ์‚ฌ์šฉ ์‹œ
    • S3 5GB / CloudFront 1TB / 20,000 GET / 2,000 PUT ๋ฌด๋ฃŒ
  • ์›” ์‚ฌ์šฉ๋Ÿ‰ ์˜ˆ์‹œ (300๋ช… ร— 5์žฅ/์ผ ร— 1MB)
    • 45GB ์ €์žฅ, 45,000 PUT โ†’ ์›” $2~3 ์ˆ˜์ค€
  • ๋น„์šฉ ์ ˆ๊ฐ ๋ฐฉ๋ฒ•
    • ์—…๋กœ๋“œ ์ „ ๋ฆฌ์‚ฌ์ด์ง•/์••์ถ•
    • ์˜ค๋ž˜๋œ ํŒŒ์ผ S3 Glacier๋กœ ์ด๋™

6๏ธโƒฃ ์ตœ์ข… ์ •๋ฆฌ

S3 + CloudFront + Presigned URL
๊ตฌ์กฐ๋ฅผ ์ ์šฉํ•˜๋ฉด ์„œ๋ฒ„ ๋ถ€ํ•˜๋ฅผ ์ตœ์†Œํ™”ํ•˜๊ณ ,
๋ณด์•ˆยท์†๋„ยท๋น„์šฉ ๋ชจ๋“  ๋ฉด์—์„œ ํšจ์œจ์ ์ธ ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


0๊ฐœ์˜ ๋Œ“๊ธ€