AWS S3 Bucket

devty·2023년 6월 8일
0

DevOps

목록 보기
8/11

사용하게 된 계기

  • 처음에는 EC2 서버에 image 파일을 경로 지정해서 올리려고 했는데 경로 서버내에서 경로 지정이 잘 안되는 경우가 존재하였다.
    • 알고보니 EC2 내에 Docker Container안에 SpringBoot를 띄워서 Docker container 안에가 Local이 되었다.
    • 즉, container 안에 경로를 설정해 줘야하는데 그 부분을 잠시 접어두고 구글링 서치를 해본 결과 AWS S3 Bucket을 사용하는 경우가 대다수라고 하였다.
  • 그래서 주변에 AWS에 다니는 개발자 친구에게 물어보았다.
  • 답변은 밑과 같았다.
    • 역시..효율성도 효율성이지만 가격에 대한 메리트면 채택하기 충분하다고 생각한다.
    • 위 개발자의 블로그는 여기이다. → youngdeveloper.tistory.com

대략적인 실행 과정

  1. S3 Bucket 생성
  2. S3 Bucket 접근 권한을 가진 IAM 사용자 생성하기
  3. AWSS3Config 파일 작성하기
  4. application.yml 파일 작성하기
  5. AWSS3Service 파일 작성하기

실행과정

1. S3 Bucket 생성

  1. AWS S3 Bucket 사이트에 접속한다.

  2. 해당 사이트에서 버킷 만들기를 클릭한다.

  3. 기본적인 정보를 입력한다.

    • 버킷 이름 → 하고싶은 이름으로 하면 됩니다.
    • ACL 활성화 → 체크
    • 객체 소유권 → 객체 라이터
      • 객체 라이터로 설정하지 않으면 ACL이 비활성화 되어 springBoot를 통해 파일 업로드 시 The bucket does not allow ACLs라는 에러가 발생한다.
    • 밑 부분에 퍼블릭 엑세스 차단 설정을 완료한다.
  4. 나머지는 Defalut 값으로 두고 생성을 완료한다.

  5. 생성된 버킷에 들어가서 권한 → 버킷 정책 → 편집을 클릭한다.

    • 버킷 정책 편집에 접속했다면 버킷 ARN은 copy 하고 정책 생성기를 클릭한다.

    • Select Type of Policy → S3 Bucket Policy

    • Principal → *

    • Action → DeleteObject, GetObject, PutObject

    • ARN → 위에서 복사한 값을 넣어주면 된다.

    • 그리고 Add Statement를 누르면 지금까지의 상태가 등록된다.

      • 밑에 Generate Policy를 클릭한다.

      • 해당 내용을 복사한다.

    • 위 복사한 내용을 버킷 정책에 입력한다.

      • 근데 방금 값으로 넣었을 경우 Resource 부분에 에러가 날수도 있다.
      • 왜냐면 해당 경로에 대한 위치를 제대로 설정하지 않았기 때문이다.
      • 따라서 "나의 버킷 이름/*” 나의 버킷 이름 뒤에 /* 를 붙여줘야지 경로설정이 제대로 된다.

2. S3 Bucket 접근 권한을 가진 IAM 사용자 생성하기

  1. AWS IAM 사이트에 접속한다.

    • 접속 후 사용자 탭에 들어가서 사용자 추가를 클릭한다.
  2. 사용자 세부 정보


- 사용자 이름 → 자기가 지정하고 싶은 이름으로 저장.

  1. 권한 설정

    • 직접 정책 연결 → AmazonS3FullAccess를 선택한다.
    • 그 다음 페이지는 그대로 넘어가고 IAM을 생성하면 된다.
  2. 생성된 IAM 사용자를 클릭힌다.

    • 보안 자격 증명 → 엑세스 키 → 엑세스 키 만들기를 클릭한다.
    • AWS 컴퓨팅 서비스에서 실행되는 애플리케이션을 선택한다.
    • 그 다음 설명 및 태그는 넘어가도 무방하다.
    • 이제 액세스 키가 생성이 되었다. 이 키들은 한번 보고 못 보니 잘 저장해두자.

3. AWSS3Config 파일 작성하기

@Configuration
public class AwsS3Config {

    @Value("${cloud.aws.credentials.access-key}")
    private String accessKey;

    @Value("${cloud.aws.credentials.secret-key}")
    private String secretKey;

    @Value("${cloud.aws.region.static}")
    private String region;

    @Bean
    public AmazonS3Client amazonS3Client() {
        BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey);
        return (AmazonS3Client) AmazonS3ClientBuilder.standard()
                .withRegion(region)
                .withCredentials(new AWSStaticCredentialsProvider(awsCreds))
                .build();
    }
}
  • 해당 파일은 S3 Bucket에서 받은 키값들과 어떤 region을 통해 접근하는지 설정하는 설정파일이다.

4. application.yml 파일 작성하기

cloud:
  aws:
    credentials:
      access-key: AWS_S3_access-key
      secret-key: AWS_S3_secret-key
    region:
      static: ap-northeast-2
    s3:
      bucket: AWS_S3_bucket
    stack:
      auto: false
  • AWS_S3_access-key → 바로 위에서 발급 받은 엑세스 키를 입력한다.
  • AWS_S3_secret-key → 바로 위에서 발급 받은 비밀 엑세스 키를 입력한다.
  • AWS_S3_bucket → 우리의 bucket 이름을 적어준다.

5. AWSS3Service 파일 작성하기

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class AwsS3Service {

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

    private final AmazonS3 amazonS3;
    private final ImageRepo imageRepo;

    private static final String IMAGE_URL_PREFIX = "https://나의 버킷 이름.s3.ap-northeast-2.amazonaws.com/";

    @Transactional
    public List<Image> uploadFile(Post post, List<MultipartFile> multipartFile) {
        List<Image> images = new ArrayList<>();

        // forEach 구문을 통해 multipartFile로 넘어온 파일들 하나씩 fileNameList에 추가
        multipartFile.forEach(file -> {
            String fileName = createFileName(file.getOriginalFilename());
            ObjectMetadata objectMetadata = new ObjectMetadata();
            objectMetadata.setContentLength(file.getSize());
            objectMetadata.setContentType(file.getContentType());

            // 이미지 DB 저장.
            String originalFilename = file.getOriginalFilename();
            String imageUrl = IMAGE_URL_PREFIX + fileName;

            Image image = Image.builder()
                    .imageName(fileName)
                    .imageUrl(imageUrl)
                    .originalImageName(originalFilename)
                    .post(post)
                    .build();

            Image saveImage = imageRepo.save(image);
            images.add(saveImage);

            try(InputStream inputStream = file.getInputStream()) {
                amazonS3.putObject(new PutObjectRequest(bucket, fileName, inputStream, objectMetadata)
                        .withCannedAcl(CannedAccessControlList.PublicRead));
            } catch(IOException e) {
                throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "파일 업로드에 실패했습니다.");
            }

        });

        return images;
    }

    @Transactional
    public void updateFile(Post post, List<Image> images, PostDto postDto) {
        images.forEach(image -> {
            amazonS3.deleteObject(new DeleteObjectRequest(bucket, image.getImageName()));
            imageRepo.delete(image);
        });

        if (postDto.getImageFiles().size() > 0 && !Objects.equals(postDto.getImageFiles().get(0).getOriginalFilename(), "")) {
            uploadFile(post, postDto.getImageFiles());
        }
    }

    @Transactional
    public void deleteFile(String fileName) {
        amazonS3.deleteObject(new DeleteObjectRequest(bucket, fileName));
    }

    private String createFileName(String fileName) { // 먼저 파일 업로드 시, 파일명을 난수화하기 위해 random으로 돌립니다.
        return UUID.randomUUID().toString().concat(getFileExtension(fileName));
    }

    private String getFileExtension(String fileName) { // file 형식이 잘못된 경우를 확인하기 위해 만들어진 로직이며, 파일 타입과 상관없이 업로드할 수 있게 하기 위해 .의 존재 유무만 판단하였습니다.
        try {
            return fileName.substring(fileName.lastIndexOf("."));
        } catch (StringIndexOutOfBoundsException e) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "잘못된 형식의 파일(" + fileName + ") 입니다.");
        }
    }
}
profile
지나가는 개발자

0개의 댓글