Presigned URL란?(미리 서명된 URL)
Client에서 S3에 직접 파일을 등록하거나(보안이슈) Client에서 전달받은 파일을 서버에서 직접 S3로 파일 업로드 하지 않고(성능 이슈)
클라이언트가 서버에 거치지 않고 파일을 저장소에 직접 업로드할 수 있어, 서버의 자원을 절약할 수 있다.
Pre-Sigend Url(미리 서명된 URL)은 다른 사람이 AWS 보안 자격 증명이나 권한이 없어도 Amazon S3 버킷에 객체를 업로드하도록 허용할 수 있도록 하는 URL입니다.
클라이언트에 요청에 따라 파일 저장경로를 서버에서 내주는 방식
S3 생성
IAM계정 (S3에 대해서 접근할 수 있는) IAM 계정 만들기
버킷명 정하기
체크박스 열어 놓기
버킷을 만든다.
정책 설정
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowPublicReadAccess",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::{버켓이름}/*"
},
{
"Sid": "AllowPresignedUpload",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::{버켓이름}/*",
"Condition": {
"StringEquals": {
"s3:authtype": "v4-authenticated"
}
}
}
]
}
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"GET",
"HEAD",
"PUT",
"POST"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": []
}
]
build.gradle
// S3
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
application.yml
cloud:
aws:
credentials:
access-key: ${S3_ACCESS} # 환경변수에서 가져오기 IAM
secret-key: ${S3_SECRET} # 환경변수에서 가져오기 IAM
region:
static: ap-northeast-2 # 서울일경우
s3:
bucket: {버킷명칭} # S3버킷명칭
endpoint: https://s3.ap-northeast-2.amazonaws.com #엔드포인트 설정 서울일 경우
stack:
auto: false
S3Config
package org.example.auctify.config;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration // 이 클래스는 Spring의 설정 클래스임을 나타냄 (Spring이 이 클래스를 설정용으로 인식)
public class S3Config {
// 애플리케이션 설정 파일(application.yml)에서 AWS Access Key를 주입
@Value("${cloud.aws.credentials.access-key}")
private String accessKey;
// 애플리케이션 설정 파일에서 AWS Secret Key를 주입
@Value("${cloud.aws.credentials.secret-key}")
private String secretKey;
// 애플리케이션 설정 파일에서 AWS 리전(region)을 주입
@Value("${cloud.aws.region.static}")
private String region;
// 애플리케이션 설정 파일에서 S3 엔드포인트(endpoint) URL을 주입
@Value("${cloud.aws.s3.endpoint}")
private String endpoint;
// AWS 자격증명(AWS Access Key와 Secret Key)을 제공하는 Bean
@Bean
@Primary // 여러 개의 자격증명 Bean이 있을 때 기본값으로 이 Bean을 사용하도록 지정
public BasicAWSCredentials awsCredentialsProvider(){
return new BasicAWSCredentials(accessKey, secretKey); // 주입된 Access Key와 Secret Key를 사용해 BasicAWSCredentials 객체를 생성
}
// AmazonS3 클라이언트를 생성하여 S3와 상호작용할 수 있게 하는 Bean
@Bean
public AmazonS3 amazonS3() {
// BasicAWSCredentials 객체 생성 (Access Key와 Secret Key 사용)
BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
// AmazonS3 클라이언트를 설정하고 반환
return AmazonS3ClientBuilder.standard()
.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endpoint, region)) // S3 클라이언트의 엔드포인트와 리전 설정
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) // 위에서 생성한 자격증명 제공
.build(); // 클라이언트 빌드 후 반환
}
}
s3Key를 받아오는 DTO (삭제할 때)
package org.example.auctify.dto.s3;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public class DeleteImageRequestDTO {
private String s3Key;
}
S3DTO
PresignedURL과 key 정보를 담음.
package org.example.auctify.dto.s3;
public class PresignedUrlResponseDTO {
private final String presignedUrl;
private final String s3Key;
public PresignedUrlResponseDTO(String presignedUrl, String s3Key) {
this.presignedUrl = presignedUrl;
this.s3Key = s3Key;
}
public String getPresignedUrl() {
return presignedUrl;
}
public String getS3Key() {
return s3Key;
}
}
S3Service
package org.example.auctify.service.s3;
import com.amazonaws.HttpMethod;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.example.auctify.controller.presigned.PresignedController;
import org.example.auctify.dto.s3.PresignedUrlResponseDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.net.URL;
import java.util.Date;
import java.util.UUID;
@Slf4j
@Component
@RequiredArgsConstructor
@Service
public class FileUploadService {
@Autowired
private AmazonS3 amazonS3;
// Presigned URL을 통해 업로드되는 파일의 이름을 저장하는 변수
private String useOnlyOneFileName;
@Value("${cloud.aws.s3.bucket}")
private String bucket; // S3 버킷 이름
@Value("${cloud.aws.region.static}")
private String location; // S3 리전 (Region)
/**
* Presigned URL을 생성하는 메서드
* @param prefix 파일을 저장할 경로 (예: "images", "documents")
* @param fileName 사용자가 업로드하려는 원본 파일명
* @return Presigned URL 문자열 (PUT 요청)
*/
public PresignedUrlResponseDTO getPreSignedUrl(String prefix, String fileName) {
// 파일 이름을 UUID를 추가한 유니크한 이름으로 변환
String s3Key = onlyOneFileName(fileName); //
// prefix(폴더 경로)가 있을 경우 경로를 포함한 파일명 설정
if (!prefix.equals("")) {
s3Key = prefix + "/" + s3Key;
}
// Presigned URL 생성 요청 객체 생성
GeneratePresignedUrlRequest generatePresignedUrlRequest = getGeneratePreSignedUrlRequest(bucket, s3Key);
URL presignedUrl = amazonS3.generatePresignedUrl(generatePresignedUrlRequest);
// AWS S3에서 Presigned URL 생성 후 반환
return new PresignedUrlResponseDTO(presignedUrl.toString() , s3Key);
}
/**
* Presigned URL 요청을 생성하는 메서드
* @param bucket 버킷 이름
* @param fileName 업로드될 파일의 S3 경로
* @return GeneratePresignedUrlRequest 객체
*/
private GeneratePresignedUrlRequest getGeneratePreSignedUrlRequest(String bucket, String fileName) {
GeneratePresignedUrlRequest generatePresignedUrlRequest =
new GeneratePresignedUrlRequest(bucket, fileName)
.withMethod(HttpMethod.PUT) // PUT 요청으로 업로드 가능
.withExpiration(getPreSignedUrlExpiration()); // Presigned URL 만료 시간 설정
// 최대 파일 크기 제한 설정 (바이트 단위)
generatePresignedUrlRequest.addRequestParameter("Content-Length", String.valueOf(20 * 1024 * 1024));
return generatePresignedUrlRequest;
}
/**
* Presigned URL의 만료 시간을 설정하는 메서드 (현재 시간 기준 2분 후 만료)
* @return 만료 시간 (Date 객체)
*/
private Date getPreSignedUrlExpiration() {
Date expiration = new Date();
long expTimeMillis = expiration.getTime();
expTimeMillis += 1000 * 60 * 2; // 현재 시간에서 2분 추가
expiration.setTime(expTimeMillis);
log.info(expiration.toString()); // 로그로 만료 시간 출력
return expiration;
}
/**
* 파일명을 UUID를 추가한 유니크한 이름으로 변환하는 메서드
* @param filename 원본 파일명
* @return 변경된 파일명 (UUID + 원본 파일명)
*/
private String onlyOneFileName(String filename) {
return UUID.randomUUID().toString() + filename;
}
/**
* S3에 저장된 파일(오브젝트)을 삭제합니다.
* @param s3Key 삭제할 S3 오브젝트 키 (폴더/UUID포함된 전체 경로)
*/
public void deleteFile(String s3Key) {
if (amazonS3.doesObjectExist(bucket, s3Key)) {
amazonS3.deleteObject(bucket, s3Key);
log.info("Deleted S3 object: {}/{}", bucket, s3Key);
} else {
log.warn("S3 object not found, skip delete: {}/{}", bucket, s3Key);
}
}
}
S3Controller
package org.example.auctify.controller.presigned;
import lombok.RequiredArgsConstructor;
import org.example.auctify.dto.s3.ImageRequestDTO;
import org.example.auctify.dto.s3.PresignedUrlResponseDTO;
import org.example.auctify.service.s3.FileUploadService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequiredArgsConstructor
public class PresignedController {
private final FileUploadService fileUploadService;
/**
* S3에게 pre-signed URL (권한) 요청하는 엔드포인트
* 프론트에서 이 URL을 받아서 AWS S3에 직접 업로드함.
*
* @param imageDTO 파일 이름 정보를 담은 DTO
* @return AWS S3에 업로드할 수 있는 Presigned URL
*/
@PostMapping("/presigned")
public ResponseEntity<PresignedUrlResponseDTO> createPresignedUrl(@RequestBody ImageRequestDTO imageDTO) {
String path = "contact"; // S3 내 저장될 폴더 경로
String s3Key = path + "/" + imageDTO.getImageName(); // 업로드될 S3 Key 생성
PresignedUrlResponseDTO presignedUrl = fileUploadService.getPreSignedUrl(path,imageDTO.getImageName());
return ResponseEntity.ok(presignedUrl); // ✅ Presigned URL과 S3 Key 반환
}
@DeleteMapping("/presigned")
public ResponseEntity<Void> deletePresigned(@RequestBody DeleteImageRequestDTO dto) {
fileUploadService.deleteFile(dto.getS3Key());
return ResponseEntity.noContent().build();
}
}
ImageRequestDTO는 이미지 명만 전달해줌. 확장자 포함해서!
API 호출해보기
Presigned URL을 받아왔다. PUT요청으로 파일을 보내면 된다.
드래그 한 부분에 파일이 저장된다.
S3이미지가 잘 나올 수 있음을 확인했다. 위에 링크보면 알겠지만 이미지 경로다.
출처
[AWS S3] 이미지 처리 1탄: Pre-signed URL로 파일 업로드 구현
📷 이미지 처리 📑 목차 [AWS S3] 이미지 처리 1탄: Pre-signed URL로 파일 업로드 구현 - 현재 포스팅 [AWS S3+Lambda] 이미지 처리 2탄: Image Resizing으로 썸네일 이미지 만들기 프로젝트에서 이미지 업로드
hello-judy-world.tistory.com
https://ji-soo708.tistory.com/24
[Spring/AWS] Pre-Signed Url을 이용하여 S3로 파일 업로드하기 (feat. NCP)
디프만 동아리에서 진행 중인 프로젝트 Bibbi(https://github.com/depromeet/14th-team5-BE)에서는 사용자 프로필이미지/피드 이미지를 업로드할 수 있는 수단이 필요했고 저희 프로젝트에서는 Pre-Signed Url 방
ji-soo708.tistory.com
AWS S3 - 버킷 권한 설정
버킷 권한 설정 목차 > 1. 버킷 정책 > 2. CORS(Cross-origin 리소스 공유) 설정 S3에 대한 버킷 버킷 정책 CORS 설정
velog.io
'AWS' 카테고리의 다른 글
IAM(Identity and Access Management) (1) | 2025.03.17 |
---|