Posts S3에 파일 업로드 하기
Post
Cancel

S3에 파일 업로드 하기

Amazon S3?

AWS S3는 아마존이 제동하는 객체 스토리지 서비스로 데이터가 저장되는 공간을 버킷(bucket)이라고 하며, 쉽게 말해 외부 저장공간이라고 할 수 있다.

스토리지 서비스이기 때문에 ls, mv, cp, rm 등 파일과 관련된 명령어만 사용이 가능하다.

스프링에서 파일 업로드

설정

스프링에서 S3의 버킷에 파일을 업로드 하기 위해서는 스프링이 버킷에 접근할 수 있어야 한다.

따라서 버킷을 생성하거나 이미 생성된 버킷에 대해 ACL을 활성화하고 퍼블릭 엑세스를 허용하여, S3만 객체 소유권을 가질 수 있도록 되어있는 설정을 해제해야 한다.

또한 IAM에서 AccessKey와 SecretKey를 발급받고 해당 키에 권한을 부여하여 키값을 가진 사용자만 접근할 수 있도록 해야한다.

자세한 방법에 참고자료의 게시물에 나와있다.

구현

  • build.gradle에 의존성 설정

    build.gradle에 AWS의 기능을 사용하기 위해 SDK의 라이브러리에 대한 의존성을 추가한다.

    1
    2
    3
    4
    5
    
    dependencies {
        ...
        implementation 'com.amazonaws:aws-java-sdk-s3:1.12.765'
      	...
    }
    
  • application.properties에 리소스 입력

    application.properties에 AWS의 기능을 사용하는 데에 필요한 값들을 넣어놓는다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    ...
    cloud.aws.credentials.accessKey=[AWS IAM에서 발급받은 Access Key]
      
    cloud.aws.credentials.secretKey=[AWS IAM에서 발급받은 Secret Key]
      
    cloud.aws.s3.bucketName=[S3 버킷 이름]
      
    cloud.aws.region.static=[AWS S3의 Region]
      
    // AWS CloudFormation을 사용하지 않으므로 false
    cloud.aws.stack.auto-=false
    ...
    

    EC2에서 Spring을 통래 S3의 기능을 사용할 경우에 AWS CloudFormation의 stack에 대한 설정을 시작하기 때문에 사용하지 않는다면 false로 꼭 적어야 한다.

  • 코드 구현

    먼저 AWS S3의 기능을 사용하기 위해 credential을 설정해주는 config파일을 만들고 빈으로 등록해준다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    @Configuration
    public class S3Config {
        @Value("${cloud.aws.credentials.accessKey}")
        private String accessKey;
        @Value("${cloud.aws.credentials.secretKey}")
        private String secretKey;
        @Value("${cloud.aws.region.static}")
        private String region;
      
        @Bean
        public AmazonS3 amazonS3() {
            AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
            return AmazonS3ClientBuilder.standard()
                    .withCredentials(new AWSStaticCredentialsProvider(credentials))
                    .withRegion(region).build();
        }
    }
    

    다음으로 빈으로 등록된 config를 이용하여 S3에 대한 기능을 코드로 작성한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    
    // 클라이언트에서 전달받은 userId로 파일 이름을 설정하고 버킷의 /image 디렉토리 내에 파일을 저장
    @RequiredArgsConstructor
    public class S3Uploader {
        private final AmazonS3 amazonS3;
      
        @Value("${cloud.aws.s3.bucketName}")
        private String bucket;
      
      	// 파일 업로드
        public String upload(MultipartFile multipartFile, String userId) {
            try {
                return putS3(multipartFile.getInputStream(), fileName, multipartFile.getSize());
            } catch (IOException e) {
                return "업로드 실패";
            }
        }
        
      	//파일 삭제
      	public void delete(String userId) {
            String fileName = "images/" + userId;
            amazonS3.deleteObject(bucket, fileName);
        }
      
      	// 업로드 구현
        private String putS3(InputStream inputStream, String fileName, long contentLength) {
          ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentLength(contentLength);
    
            amazonS3.putObject(new PutObjectRequest(bucket, fileName, inputStream, metadata)
                  .withCannedAcl(CannedAccessControlList.PublicRead));
          	// ACL설정을 PublicRead로 하여 다른 위치에서도 해당 파일에 접근 가능하도록 한다.
            return amazonS3.getUrl(bucket, fileName).toString();
        }
    }
    

    이 때, S3에 객체를 전달할 때에는 클라이언트로부터 전달받은 MultipartFile타입의 객체를 전달하는 것이 아니라 파일의 InputStream을 전달하며, metadata를 함께 전달한다.

    MultipartFile타입의 객체를 File로 변환한 뒤에 File타입의 객체를 전달하는 방법도 있지만 파일을 전달하기 위한 임시파일을 생성해야 하므로 효율성이 떨어지게 된다.

    InputStream을 통해 버킷에 파일을 업로드 하기 위해서는 반드시 contentLengthmetadata에 명시해 주어야 한다.

    버킷에 이미 같은 이름의 파일이 존재하면 해당 파일을 덮어쓰기 처리되기 때문에 다르게 저장되어야 하는 파일의 이름이 중복되지 않도록 유의해야 한다.(필요하다면 UUID를 생성하여 파일명에 포함)

    또한 삭제 시 해당 파일이 버킷에 존재하지 않더라도 에러메시지 대신 성공메시지가 발생한다.


참고자료 :

https://velog.io/@yes_jihyeon

https://www.catsriding.com

https://antstudy.tistory.com

https://ittrue.tistory.com

This post is licensed under CC BY 4.0 by the author.

스프링에서 REST API와 CRUD 구현

돈두댓 프로젝트 회고