https://github.com/E24I/TOGEDOG
GitHub - E24I/TOGEDOG: 🐕내 반려동물과 추억을 쌓을 수 있는 곳. TOGEDOG
🐕내 반려동물과 추억을 쌓을 수 있는 곳. TOGEDOG. Contribute to E24I/TOGEDOG development by creating an account on GitHub.
github.com
S3 presignedURL 도입기
진행 중인 프로젝트는 반려견, 반려묘에 관한 웹 어플리케이션이라
작성자의 피드에 사진, 동영상 등을 많이 올릴 것으로 예상되었다.
그래서 우리 팀은 이미지 업로드의 기능을 구현하기로 했다.
먼저 방법들을 알아봤는데 총 3가지 방법이 눈에 띄였다.
이미지 업로드 방법
1. 서버 경유 업로드
클라이언트에서 이미지 파일(MultipartFile)을 우리 서버로 전송하고 그걸 우리
서버에서 s3에 올리는 방식
이미 구현해본 적이 있어서 쉽고 S3의 내구성 및 보안성(URL의 시간제한)의 장점이 있다.
서버를 경유하기에 서버 부하가 증가하고, 대용량 파일 처리 시 성능 저하가 예상된다.
2. 서드파티 클라우드 서비스
Cloudinary 같은 이미지 관리 서비스 사용하면 이미지 최적화와 ui로 관리하기 편하다.
그렇지만 제일 문제인 비용이 너무 높다..
3. S3 Presigned URL 업로드
클라이언트가 이 URL로 직접 업로드 방식
장점으로는 서버의 부하가 낮고 S3의 내구성 및 보안성(URL의 시간제한)이 있다.
단점으로는 역시 우리 서버에서 업로드 시점에서 용량을 제한할 수 없다.
S3 채택
호완성 및 안정성
배포 환경 생태계가 겹치는 aws를 더 선호했고,
99.999999999%의 내구성과 99.99%의 가용성을 제공
이 가용성 수치라면
365로 나눴을 때,하루 장애 시간이 약 8.64초로, 높은 가용성을 제공한다.
매우 안전한 수치이다.
비용 계산

이러한 이 점들로 우리는 S3를 선택하기로 했고,
서버 경유 업로드 vs s3 PresignURL
서버 경유 업로드가 느릴 거라고 생각이 들고, 바로 s3로 하는 게 직관적으론 더 나은 결론인 느낌든다.
하지만 우리는 개발자이다. 다들 직접 눈으로 확인하길 원했고 어느 정도의 성능 변화가 있는 지 다들 흥미로워 했다.
실제로 각각의 프로토타입을 만들어 팀원들과 함께 테스트해보기로 했다.
1.MultipartFile을 서버로 업로드하는 경우
- 흐름
클라이언트 → Spring 서버 → S3 (또는 로컬 저장소) - 장점
- 업로드 로직을 서버에서 통제 가능
- 인증/검증을 서버에서 처리
- 단점
- 네트워크 두 번 사용됨
- 클라이언트 → 서버 (보통 수백 ms ~ 1~2초)
- 서버 → S3 (수백 ms ~ 1초 이상)
- 서버에 부하가 큼 (트래픽, 메모리 등)
- 네트워크 두 번 사용됨
S3 presignedURL

S3 Presigned URL은
AWS S3에서 제공하는 기능으로 특정 권한(예: 파일 업로드, 다운로드)을 가진 URL을 미리 서명하여 사용자에게 제공하는 방식입니다. 이 URL은 AWS 자격 증명 없이도 제한된 시간 동안 S3에 접근할 수 있게 해줍니다.
즉 서버를 거지치 않고 사용자가 우리 클라우드 저장소로 쓰고 있는 s3에 접근해 사진을 올릴 수 있다는 뜻이다.
그 s3에 접근할 수 있는 권한을 임시로 URL 형태로 만들어서 주는 것이 S3 Presigned URL이다.
결국 우리는 이러한 성능 차이로 S3 Presigned URL을 도입하기로 했다.
Presigned URL 과정
고려 사항


1. 갯수 제한
먼저 우리는 피드에 올릴 수 있는 사진의 양과 동영상을 제한하기로 했다.
우리는 sns 형식의 피드와 비슷해서 이미지의 경우 최대 5개와 동영상의 경우 최대 1개로 설정했다.
2.용량 제한
아무리 갯수를 제한한다고 해도 말도 안되는 크기의 사진이나 동영상을 올리면 의미가 없다고 판단했다.
그래서 용량을 제한하려고 하는 도중 백에서는 막을 방법이 떠오르질 않았다..
왜냐하면 서버에서 하는 건 s3와 통신해서 서명된 URL를 만들어 s3에 접근할 수 있도록 하는 것이다.
그래서 용량이나 사진의 갯수를 제한하는 건 프론트의 몫이였다.
구현 설명
1.config

먼저 s3에 접근하기 위한 accessKey와 secretKey region이 필요하다.
환경변수 설정을 해주고,

BasicAWSCredentials은
AWS SDK 에서 S3에 접근할 때 필요한 인증 정보를 제공하는 클래스다
AmazonS3Client는
S3 버킷에 파일 업로드, Presigned URL 생성, 파일 조회 등 S3 API를 호출하는 핵심 인터페이스!
지역과 위에 만든 자격증명으로 amazonS3 객체를 생성할 수 있다
2. controller

package togedog.server.global.image;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/presigned-url")
@Slf4j
@RequiredArgsConstructor
public class PresignedUrlController {
private final PresignedUrlService presignedUrlService;
@PostMapping()
public ResponseEntity<String> generatePresignedUrl(@RequestBody ImageNameDTO imageNameDTO) {
String generatedUrl = presignedUrlService.getPreSignedUrl("", imageNameDTO.getImageName());
return ResponseEntity.ok(generatedUrl);
}
}
클라이언트에서 이미지 파일을 업로드하고 싶을 때
DTO로는

이미지이름을 받아줬는데, service 코드에서 s3에서 get할 때를 대비했고, 유일한 Id값으로 올려야 한다.
3.service
package togedog.server.global.imagetest;
import com.amazonaws.HttpMethod;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.Headers;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.Serializable;
import java.net.URL;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Service
public class AmazonS3TestService {
private final AmazonS3Config amazonS3Config;
@Autowired
public AmazonS3TestService(AmazonS3Config amazonS3Config) {
this.amazonS3Config = amazonS3Config;
}
public Map<String, Serializable> getPreSignedUrl(String fileName) {
String encodedFileName = fileName + "_" + LocalDateTime.now();
String objectKey = "test/" + encodedFileName;
Date expiration = new Date();
long expTimeMillis = expiration.getTime();
expTimeMillis += (3 * 60 * 1000); // 3분
expiration.setTime(expTimeMillis); // URL 만료 시간 설정
GeneratePresignedUrlRequest generatePresignedUrlRequest =
new GeneratePresignedUrlRequest(amazonS3Config.getBucketName(), objectKey)
.withMethod(HttpMethod.PUT)
.withExpiration(expiration);
AmazonS3 amazonS3 = amazonS3Config.amazonS3();
String preSignedUrl = amazonS3.generatePresignedUrl(generatePresignedUrlRequest).toString();
Map<String, Serializable> resultMap = new HashMap<>();
resultMap.put("preSignedUrl", preSignedUrl);
resultMap.put("encodedFileName", encodedFileName);
return resultMap;
}
}
2가지 핵심 기능이 있다.


업로드 가능한 Presigned URL 생성하고 리턴해주기

버킷과 파일 키를 설정하고
HttpMethod - PUT 사용
보안을 위해 만료 시간 설정 - getPreSignedUrlExpiration()에서 가져온다
PublicRead ACL은 업로드 후 누구나 파일 접근 가능하게 해주면 된다.
3. getPreSignedUrlExpiration

보안을 위해서 PreSignedUrl에 접근 시간을 2분으로 설정함
그 결과!

put으로 우리 버킷의 s3에 접근할 수 있는 서명된 URL가 내려질거고,
클라이언트에서는 이 서명된 URL로 PUT 요청으로 사진을 보내면 s3에 저장된다.
이 코드를 짤 때는 정신없이 찾아보며 짜느라 몰랐는데 리팩토링할 부분이 많아보인다..
번외- 에러

내가 보낼 땐 항상 잘되던데 클라이언트 측에서 계속 access key 관련 에러가 났다.
access key는 여러 번 확인해봐도 정상이길래 다른 곳에서 계속 에러를 찾았다.

둘 다 처음 해보는 거라 감도 안오고 자료도 그렇게 많지 않아서.. 엄청 삽질했다..

진짜 의외로 환경변수 문제..
iam role 때문에 내가 직접 접근할 수 없었고,
담당자 분이 확인하셨는데 여러 번 이상 없다고 하셔서 ㅠㅠ
이런 작은 철자때문에 엄청 시간을 날렸지만

그래도 진짜 서로 처음 만져보는데 삽질도 하고 정말 값진 해결과정이였다..
번외 2 - 프론트 s3 담당자 분도 다른 에러..



결과적으로 이분도 content-type 오타였다..
https://heewon26.tistory.com/377
AWS S3 Presigned URL
개요 presigned URL 이란? presigned URL 은 왜 필요할까? presigned URL 이용해 presigned POST를 해보자 Presigned URL 이란 Presigned URL 이란 AWS 자원에 대한 접근 권한을 제공하기 위해서 사용되는 이름 그대로 사전
heewon26.tistory.com
'트러블 슈팅 및 도입기' 카테고리의 다른 글
| My Record - 배포 후 Oauth2 500 에러? -트러블 슈팅 (0) | 2025.04.08 |
|---|---|
| 투겟독 -댓글 고정 반환 시 에러 트러블 슈팅 ! (0) | 2023.12.26 |
| 프로젝트 - 알림 시스템 도입기 (0) | 2023.11.19 |
| 401 vs 403 인증과 권한의 미묘한 차이 (0) | 2023.10.20 |
| 삐삐 프로젝트 - jwt를 더 안전하게! RTR도입기 (0) | 2023.10.10 |