728x90
S3 Presigned URL로 파일 업로드 구현
결론부터
클라이언트가 서버를 거치지 않고 S3에 직접 파일을 올리게 하려면 Presigned URL을 쓴다. 서버는 "이 URL로 10분간 이 키에 업로드해도 좋다"는 서명된 URL만 발급하고, 실제 바이트는 클라이언트가 S3로 직접 전송한다. 서버 대역폭과 메모리를 아끼고, 대용량 업로드에서 특히 유리하다. 자바에서는 AWS SDK for Java 2.x의 S3Presigner로 발급한다.
왜 Presigned URL인가
파일을 서버가 받아서 다시 S3로 올리면, 업로드 트래픽이 서버를 두 번 통과한다. 파일이 크거나 동시 업로드가 많으면 서버 메모리와 네트워크가 병목이 된다. Presigned URL은 이 경로를 없앤다. 서버는 인증·권한만 책임지고, 데이터 전송은 클라이언트와 S3가 직접 처리한다.
흐름은 세 단계다. ① 클라이언트가 서버에 업로드 URL 요청 → ② 서버가 Presigned URL 발급 → ③ 클라이언트가 그 URL로 S3에 직접 PUT.
의존성과 설정
dependencies {
// 버전은 최신 릴리스로 확인 후 사용
implementation platform('software.amazon.awssdk:bom:2.31.0')
implementation 'software.amazon.awssdk:s3'
}
aws.s3.bucket=my-upload-bucket
aws.region=ap-northeast-2
서버: URL 발급
S3Presigner로 PUT용 Presigned URL을 만든다.
@Service
public class S3UploadService {
private final S3Presigner presigner;
private final String bucket;
public S3UploadService(
@Value("${aws.s3.bucket}") String bucket,
@Value("${aws.region}") String region) {
this.bucket = bucket;
this.presigner = S3Presigner.builder()
.region(Region.of(region))
.build();
}
public String createUploadUrl(String key) {
PutObjectRequest objectRequest = PutObjectRequest.builder()
.bucket(bucket)
.key(key)
.build();
PutObjectPresignRequest presignRequest = PutObjectPresignRequest.builder()
.signatureDuration(Duration.ofMinutes(10)) // URL 유효 시간
.putObjectRequest(objectRequest)
.build();
return presigner.presignPutObject(presignRequest).url().toString();
}
}
컨트롤러는 키를 만들어 URL을 돌려준다.
@RestController
@RequestMapping("/uploads")
public class UploadController {
private final S3UploadService service;
public UploadController(S3UploadService service) {
this.service = service;
}
@PostMapping("/presign")
public Map<String, String> presign(@RequestParam String filename) {
String key = "uploads/" + UUID.randomUUID() + "-" + filename;
return Map.of("uploadUrl", service.createUploadUrl(key), "key", key);
}
}
클라이언트: 직접 업로드
발급받은 URL로 PUT 하면 끝이다.
const res = await fetch("/uploads/presign?filename=" + file.name, { method: "POST" });
const { uploadUrl } = await res.json();
await fetch(uploadUrl, { method: "PUT", body: file });
주의사항
- CORS: 브라우저에서 직접 PUT 하려면 S3 버킷 CORS 설정에 해당 오리진과
PUT메서드를 허용해야 한다. - Content-Type: 발급 시 지정했다면 업로드 요청도 같은 헤더를 보내야 서명이 일치한다. 불일치는
SignatureDoesNotMatch로 조용히 실패한다. - 키 검증: 클라이언트가 임의 키를 못 쓰도록, 키 생성은 반드시 서버에서 한다.
- 유효 시간: 짧게(수 분~십 분) 잡아 URL 유출 위험을 줄인다.
마무리
Presigned URL은 '권한은 서버가, 전송은 클라이언트가'라는 역할 분리로 서버 부하를 크게 줄인다. 업로드가 잦거나 파일이 큰 서비스라면, 서버를 경유하는 방식보다 이 패턴이 대부분 낫다. 같은 방식으로 다운로드용(presignGetObject)도 발급할 수 있다.
728x90
'ETC > AWS' 카테고리의 다른 글
| AWS EC2 IAM Role 설정: Access Key 없이 Spring Boot에서 S3 접근하기 (0) | 2026.07.30 |
|---|---|
| AWS Lambda MicroVM, AI 생성 코드 안전하게 실행하는 법 (0) | 2026.07.24 |
| AWS RDS vs EC2 직접 설치, DB 운영 방식 비교 (0) | 2026.07.10 |
| AWS EC2에 Spring Boot 배포하는 전체 흐름: 초보자를 위한 서버 배포 입문 (0) | 2026.06.25 |
| Amazon linux EC2 jenkins와 JDK 11 설치 (0) | 2022.08.18 |