ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [AWS + Spring] Spring Boot에서 S3와 CloudFront를 이용한 파일 업로드 (2)
    Spring 2023. 9. 3. 19:04
    반응형

     

    이전 포스팅에서 AWS CloudFront 및 S3를 생성하고, 정책을 생성해서 적용까지 완료 했습니다.
    해당 포스팅에서는 Spring Boot 설정을 하고
    Presigned URL 생성 API 까지 구현 해보겠습니다.

     

     

    AWS CloudFront 및 S3 및 정책 생성은 이전 포스팅을 참고 하시길 바랍니다.
    https://developer-been.tistory.com/36

     

    [AWS + Spring] Spring Boot에서 S3와 CloudFront를 이용한 파일 업로드 (1)

    Spring Boot 애플리케이션에서 파일 업로드 기능을 구현하고, 이를 AWS S3와 CloudFront를 활용하여 파일을 안정적으로 저장하고 배포하는 방법을 설명합니다. 이를 통해 애플리케이션의 성능과 안정성

    developer-been.tistory.com

     

     

     

    들어가기 앞서서 본 포스팅에서 하고자 하는 파일 업로드 방식에 대해 설명 하겠습니다.

     

    Presinged URL은 URL은 미리 서명되어 있으며, 설정된 유효 기간 동안만 사용할 수 있습니다. 

    Presigned URL을 생성한 후에는 이를 클라이언트에게 제공하여 사용할 수 있습니다.

    서명된 URL이므로 클라이언트는 별도의 AWS 자격 증명이나 권한을 다룰 필요 없이
    안전하게 파일 작업을 수행할 수 있습니다.

    따라서 Presigned URL을 통해 해당 웹 애플리케이션에서 클라이언트에게 안전하게 파일 업로드 기능을
    제공할 수 있습니다.

     

     

     

    Presigned URL을 이용한 File Upload 프로세스는 아래와 같습니다.

    1. Client (React) 에서 Spring Boot Server에 생성한 "Presigned-URL 생성 API"를 호출합니다.

    2. Server에서는 "Presigned-URL 생성 API" 응답값으로 presignedUrl과  CDN 주소를 응답 합니다.

    3. Client에서는 Return 받은 Presigned Url에 File을 Upload 합니다.

    4. File을 성공적으로 업로드 한 뒤, CDN 주소를 특정 Server Api를 호출할 때 Parameter에 담아서 전송하고,
        Server에서는 해당 CDN 주소를 DB에 저장합니다.

     

     

    실습환경

    • Intellij Ultimate
    • JAVA 17
    • Spring Boot 2.7.3
    • Maven
    • MySQL

    📌 시작하기

     

    5. Spring Boot 프로젝트 설정

     

    Maven Dependency 추가

    <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>aws-java-sdk-s3</artifactId>
        <version>1.12.131</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>

    Presigned Url 생성을 위해서 'aws-java-sdk-s3' Dependency와

    application.yml에 AWS S3및 CloudFront 정보를 Custom Format으로 입력하기 위해서

    'spring-boot-configuration-processor' Dependency를 추가 해줍니다.

     

     

    application.yml

    service:
        cdn:
            url: {CloudFront 대체 도메인 입력}

    cdn 경로 설정

     

     

    cloud:
      aws:
        credentials:
          access-key: {이전 포스팅에서 생성한 IAM 계정의 Access Key 입력}
          secret-access-key: {이전 포스팅에서 생성한 IAM 계정의 Secret Key 입력}
        s3:
          bucket: {이전 포스팅에서 생성한 S3 도메인 입력}
          presigned-url-expired: 600000
        region:
          static: ap-northeast-2

    Aws Credentials 및 S3 정보를 설정 합니다.

     

     

    AwsConfig.class

    @Configuration
    @RequiredArgsConstructor
    public class AwsConfig {
    
        private final Environment environment;
    
        @Value("${cloud.aws.s3.presigned-url-expired}")
        private Long expireTime;
    
        @Value("${cloud.aws.s3.bucket}")
        private String bucket;
    
        @Value("${cloud.aws.credentials.access-key}")
        private String accessKey;
    
        @Value("${cloud.aws.credentials.secret-access-key}")
        private String secretKey;
    
        @Value("${cloud.aws.region.static}")
        private String region;
    
        @Value("${service.cdn.url}")
        private String cdn;
    
        @Bean
        public AwsComponent amazonS3Client() {
            BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
    
            return AwsComponent
                    .builder()
                    .bucketName(bucket)
                    .uploadExpiredTime(expireTime)
                    .environment(environment)
                    .accessKey(accessKey)
                    .secretKey(secretKey)
                    .cdn(cdn)
                    .amazonS3(
                            AmazonS3ClientBuilder
                                    .standard()
                                    .withRegion(region)
                                    .enablePathStyleAccess()
                                    .withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
                                    .build())
                    .build();
        }
    }

    1. yml에서 입력한 정보들로 aws configuration을 설정 합니다.

    2. @Value Annotation으로 설정 값들을 불러와 injection 합니다.

    3. BasicAWSCredentials 클래스는 AWS 자격 증명을 나타내는 클래스입니다. 이 클래스를 사용하여 액세스 키와 비밀 키를 저장하고 AWS 서비스와 통신할 때 이 자격 증명을 사용할 수 있습니다.

    4. S3 client를 구성하고 Bean으로 등록합니다.

     

     

     

    AwsComponent.class

    @Slf4j
    @Builder
    public class AwsComponent {
    
        private Environment environment;
    
        private String bucketName;
    
        private long uploadExpiredTime;
    
        private AmazonS3 amazonS3;
    
        private String accessKey;
    
        private String secretKey;
    
        private String cdn;
    
        public PresignedUrl createPresignedUrl(CreateUploadPathRequest request) {
            String profile = Arrays.toString(environment.getActiveProfiles());
            if (!StringUtils.hasText(profile)) throw new IllegalArgumentException("active profiles cannot be null.");
            String fileName = request.getFileName();
            String ext = fileName.substring(fileName.lastIndexOf(".") + 1);
            String path = String.format("upload/%s/%s/%s",
                    request.getType().name().toLowerCase(),
                    LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")),
                    UUID.randomUUID().toString().replace("-", "") + "." + ext);
            return new PresignedUrl(
                    generatePresignedUrl(path),
                    String.format("%s/%s", cdn, path)
            );
        }
    
        public String generatePresignedUrl(String path) {
            if (path.startsWith("/")) {
                path = path.substring(1);
            }
            GeneratePresignedUrlRequest generatePresignedUrlRequest =
                    new GeneratePresignedUrlRequest(bucketName, path)
                            .withMethod(HttpMethod.PUT)
                            .withExpiration(makeExpirationTime(uploadExpiredTime));
            return amazonS3.generatePresignedUrl(generatePresignedUrlRequest).toString();
        }
    
        private Date makeExpirationTime(long expiredTime) {
            Date expiration = new Date();
            long expireTimeMillis = expiration.getTime();
            expireTimeMillis += expiredTime;
            expiration.setTime(expireTimeMillis);
            return expiration;
        }
    }

     

     

    1. CreatePresignedUrl Method

    createPresignedUrl 메서드는 파일 업로드를 위한 프리사인드 URL을 생성하는 역할을 합니다. 

    이 메서드가 수행하는 작업을 살펴보겠습니다.

     

    - 프로필 확인: 다른 환경(예: 개발, 프로덕션)에서 올바르게 프로필이 설정되었는지 확인하는 데 유용합니다. 활성 프로필이 없으면 Exception을 발생 시킵니다.

    - 파일 이름 및 확장자 추출: 파일 경로 생성을 위해 제공된 CreateUploadPathRequest에서 파일 이름과 확장자를 추출합니다. 

    - 경로 생성: 업로드된 파일을 위한 고유한 경로를 생성합니다. 경로는 다음과 같은 구성 요소로 이루어져 있습니다:

    * 'upload' 라는 기본 디렉터리.
    * request.getType()을 기반으로 한 하위 디렉터리 (파일 유형을 나타내는 Enum Type).

    * 'yyyy/MM/dd' 형식으로 현재 날짜를 기반으로 한 하위 디렉터리.

    * UUID 및 원래 파일의 확장자를 사용하여 생성된 고유한 파일 이름.

     

    - 프리사인드 URL 생성: 프리사인드 URL을 생성하기 위해 generatePresignedUrl 메서드를 호출합니다. 이 메서드는 파일 경로에 대한 프리사인드 URL을 생성하며, 해당 URL을 통해 스토리지 서비스의 파일에 일시적으로 액세스할 수 있게 됩니다.

    - CDN 통합: 메서드는 파일이 접근 가능한 콘텐츠 전송 네트워크(CDN)를 가리키는 URL을 생성합니다. 이 URL에는 CDN 기본 URL과 생성된 파일 경로가 포함됩니다.

     

     

     

    2. generatePresignedUrl

    generatePresignedUrl 메서드는 주어진 경로(path)에 대한 프리사인드 URL을 생성하는 역할을 합니다.

    이 메서드의 주요 기능은 다음과 같습니다.

     

    - 경로 정규화: path가 슬래시(/)로 시작하는 경우, 슬래시를 제거하여 경로를 정규화합니다. 프리사인드 URL을 생성할 때 슬래시로 시작하면 예기치 않은 동작을 일으킬 수 있으므로 이를 방지하기 위한 처리입니다.

    - 프리사인드 URL 요청 생성: GeneratePresignedUrlRequest 객체를 생성합니다. 이 객체는 프리사인드 URL을 생성하는 데 필요한 정보를 포함합니다.

    - bucketName: 프리사인드 URL이 지정된 버킷에 대한 액세스 권한을 부여하도록 설정합니다.

    - path: 프리사인드 URL이 생성될 경로를 지정합니다.

    - withMethod(HttpMethod.PUT): 프리사인드 URL을 생성할 때 사용할 HTTP 메서드를 PUT으로 설정합니다.

    - withExpiration(makeExpirationTime(uploadExpiredTime)): 프리사인드 URL의 만료 시간을 설정합니다. makeExpirationTime 메서드를 사용하여 지정된 만료 시간을 계산하고 설정합니다.

    - 프리사인드 URL 생성: 생성된 GeneratePresignedUrlRequest 객체를 사용하여 Amazon S3 클라이언트(amazonS3)에서 프리사인드 URL을 생성합니다. 이 URL은 클라이언트에게 파일 업로드 또는 다운로드 작업을 수행할 수 있는 임시적인 액세스를 제공합니다.

    - 문자열 반환: 생성된 프리사인드 URL을 문자열로 변환하여 반환합니다. 이 URL은 클라이언트 애플리케이션에 전달되어 파일 업로드 작업에 사용됩니다.

     

     

    3. makeExpirationTime

    - properties에 설정한 uploadExpiredTime을 현재 시간에 더해서 프리사인드 URL의 만료 시간을 설정합니다.

     

     

     

    이제, Aws Configuration 설정도 끝냈고, Presigned Url 생성을 위한 S3 Client Component도 구현 했습니다.

    최종적으로 Presigned Url 생성을 위한 API Controller는 아래와 같습니다.

     

     

    CreateUploadPathRequest.class

    @Getter
    public class CreateUploadPathRequest {
    
        @NotNull
        @Schema(description = "업로드 유형")
        private UploadPathType type;
    
        @NotBlank
        @Schema(description = "파일명")
        private String fileName;
    }

     

    1. UploadPathType은 업로드 유형 enum type class 입니다. 예로 POST, COMMENT 등이 있습니다.

    해당 정보는 file path를 생성하는데 사용 됩니다.

    2. @NotNull , @NotBlank Annotation으로 필수값 설정을 했고, @Valid Annotation으로 검증합니다.

     

     

    PresignedUrl.class

    @AllArgsConstructor
    public class PresignedUrl {
    
        @Schema(description = "presigned-url")
        private String presigned;
    
        @Schema(description = "cdn 주소")
        private String cdn;
    
    }

    1. Presigned Url 생성 API의 response class 입니다. 

    2. presigned field는 생성한 presignedUrl이 저장 됩니다.

    3. cdn field는 클라이언트에게 전달 하는 cdn 주소이며, 실제 S3 객체 접근시 해당 cdn 주소를 통해 접근 하게 됩니다.

     

     

     

    AwsController.class

    @RestController
    @RequestMapping("/api/aws")
    @RequiredArgsConstructor
    @Api(tags = "AWS API")
    public class AwsController {
    
        private final AwsComponent awsComponent;
    
        @Operation(summary = "Presigned Url 생성", description = "Presigned Url 생성")
        @PostMapping("/presigned-url")
        public PresignedUrl createPresignedUrl(
                @Parameter(description = "요청 정보") @RequestBody 
                @Valid CreateUploadPathRequest request
        ) {
            if (!request.getFileName().contains(".")) 
            	throw new ServiceException(ResultCode.INVALID_FILE_NAME);
            return awsComponent.createPresignedUrl(request);
        }
    }

     

    1. @Api, @Operation, @Parameter Annotation은 Swagger Annotation입니다.

    2. @Valid Annotation으로 request Object를 검증 합니다.

    3. 확장자 추출을 위해 request.fileName field에 "."이 포함 되어 있지 않으면 사용자 정의 Exception을 발생 시킵니다.

    4. awsComponent의 createPresignedUrl 을 호출 합니다.

    5. PresignedUrl Response Class를 응답합니다.

     

     

    이러한 방식으로 createPresignedUrl 메서드를 통해서 주어진 경로에 대한 안전하고 일시적인 프리사인드 URL을 생성합니다. 이를 통해 클라이언트는 파일을 업로드하거나 다운로드하기 위해 프리사인드 URL을 사용할 수 있습니다.

     

    AWS CloudFront, aws s3, CloudFront with S3, file upload, react file upload, S3, S3 Cors 설정, S3 with CloudFront File Upload, Spring Boot, Spring Boot File Upload

    반응형

    댓글

Designed by Tistory.