Presigned URL란?(미리 서명된 URL)

https://velog.io/@invidam/S3-Presigned-Url-%EB%8F%84%EC%9E%85%ED%95%98%EA%B8%B0

 

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

+ Recent posts