본문 바로가기
프로젝트/AeStagram

Spring Boot AWS S3 연동

by 에어컨조아 2021. 10. 22.

진행하던 프로젝트에서 정적 이미지를 업로드 하는 기능이 필요하여 가장 많이 사용하는 AWS S3를 어떻게 구성하고 사용했는지 공유하려고 합니다.

🤔어떤 S3를 사용할까?

S3는 AWS뿐만아니라 대표적으로 MS, Google 등 많은 곳에서 제공하고있습니다.

그중 AWS를 사용하기로 결정한 이유는 다음과 같습니다.

  • 클라우드 서비스 중 AWS를 사용한적이 없습니다...(Naver Cloud Platform, GCP) 등 사용한 적이 없어 이번엔 AWS와 친해보려고 합니다.
  • 많은 기업들이 사용하고 있습니다.
  • 1년동안 무료로 사용할 수 있습니다.

AWS의 S3로 결정하고 정보를 찾던 도중 GCS는 월 5G까지 무료라고...

하지만 이번엔 1년동안 AWS와 친해져서 익숙해지려고 합니다.

S3같은 서비스를 사용하는 이유

개인 프로젝트로 만들고 있는 서비스도 SNS 기반의 서비스 입니다. SNS 서비스 특징으로는 많은양의 정적인 이미지들을 서버에 저장하고 언제든지 확인할 수 있어야 합니다.

기본적으로 피드 한개에 이미지가 1개라고 가정했을 시 10만명의 유저가 사용하는 서비스에서는 10만개의 이미지를 저장해야 합니다. 이를 S3가 없이 local서버에 저장하게 된다면 서버의 저장공간은 순식간에 부족해질 것입니다.

이러한 문제점들을 해결하기위해 AWS S3 같은 Storage 서비스를 이용하는 것이라 생각합니다.

그럼 S3의 사용이점에 대해서 몇 가지 살펴보겠습니다.

  • 서로 다른 Region에 자체적으로 복사본을 생성하여 저장하기 때문에 99.999999999%의 데이터 내구성을 제공합니다.
  • EC2같은 VM을 사용하여 처리할 수 있지만 빠르게 Api를 사용하여 CRUD 가능하고, 성능 저하 없이 비용을 절감할 수 있습니다.
  • 데이터 보안 규정을 준수하여 안전하게 보호할 수 있습니다.

S3 버킷 생성

Amazon S3는 버킷별로 파일들을 관리합니다. 버킷은 리전별로 생성이 가능하며, 같은 리전 내에서는 유일해야 합니다.

그럼 생성방법에 대해서 간략하게 알아보겠습니다.

1.버킷이름과 리전은 각자 본인의 상황에 맞게 설정합니다.
2.버킷의 퍼블릿 차단 액세스를 해제합니다.

3.만들어진 버킷에 권한 탭을 통해서 권한을 수정합니다.
1. 이때 정책 생성기를 통해서 수정합니다.

ACL(액세스 제어 목록)에 등록된 사용자가 접근할 수 있도록 객체 소유권을 설정합니다.

4.Application이 S3에 접근하기위해 IAM 사용자 권한을 추가합니다.

내보안 자격증명에 들어가서 사용자를 추가합니다.

마지막으로 정책을 위와같이 설정 후 액세스 키, 비밀 엑세스 키를 잘 관리하여 나중에 사용합니다.

SpringBoot Application 설정

build.gradle

//AWS S3 의존성 추가
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-aws', version: '2.2.1.RELEASE'

S3UploaderUtils.java

Amazon S3에 파일 업로드 처리부분은 별도의 Utils로 분리하였습니다.

@RequiredArgsConstructor
@Component
public class S3UploaderUtils {

  private final AmazonS3Client amazonS3Client;

  @Value("${cloud.aws.s3.bucket}")
  public String bucket;

  public FileUploadDto upload(MultipartFile multipartFile, String dirName) throws IOException {
      File uploadFile = convert(multipartFile)
          .orElseThrow(
              () -> new IllegalArgumentException("error: MultipartFile ->File convert fail"));

      return upload(uploadFile, dirName);
  }

  public void delete(String filePath) {
      amazonS3Client.deleteObject(bucket, filePath);
  }

  //S3로 파일 업로드 하기
  private FileUploadDto upload(File upLoadFile, String dirName) {
      // S3에 저장된 파일 이름
      String filePath = dirName + "/" + UUID.randomUUID() + upLoadFile.getName();
      // S3로 업로드
      String uploadImageUrl = putS3(upLoadFile, filePath);
      removeNewFile(upLoadFile);

      return FileUploadDto.builder()
          .fileFullPath(filePath)
          .fileUrl(uploadImageUrl)
          .build();
  }

  private String putS3(File upLoadFile, String fileName) {
      amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, upLoadFile).withCannedAcl(
          CannedAccessControlList.PublicRead));
      return amazonS3Client.getUrl(bucket, fileName).toString();
  }

  private void removeNewFile(File targetFile) {
      if (targetFile.delete()) {
          System.out.println("file delete success");
          return;
      }
  }

  private Optional<File> convert(MultipartFile file) throws IOException {
      File convertFile = new File(
          System.getProperty("user.dir") + "/" + file.getOriginalFilename());
      if (convertFile.createNewFile()) {
          //FileOutputStream 데이터를 파일에 바이트 스트림으로 저장한다.
          try (FileOutputStream fos = new FileOutputStream(convertFile)) {
              fos.write(file.getBytes());
          }
          return Optional.of(convertFile);
      }
      return Optional.empty();
  }
}

파일 업로드 시 Client에 받은 MultipartFile로는 곧바로 S3에 저장할 수 없으므로 File형태로 변환하여 업로드 하였습니다.

이때 파일 이름은 중복이 발생되지 않도록 UUID를 사용하여 파일의 중복을 사전에 방지하였습니다.

application-aws.yml

cloud:
  aws:
    credentials:
      instance-profile: false
      access-key: S3 버킷 액세스 키
      secret-key: S3 버킷 비밀 액세스 키
    s3:
      bucket: aestagram
    region:
      static: ap-northeast-2
    stack:
      auto: false

자세한 코드는 해당 프로젝트에서 확인하시면 됩니다.

Reference

https://jojoldu.tistory.com/300
https://devlog-wjdrbs96.tistory.com/323

댓글