[TIL] Springboot에서 S3에 사진 업로드하기

phdljr·2023년 11월 24일
0

TIL

목록 보기
36/70

프로젝트를 진행하던 도중, 프로필 사진을 업로드하는 기능을 구현하게 되었다.

사진은 AWS의 S3에 저장시켜야 한다.

스프링부트에서 S3에 사진을 업로드하고 저장된 사진의 URI를 가져오며 삭제까지 구현해보는 시간을 가져본다.


S3 설정

  • S3 버킷을 public으로 생성한 뒤, 다음과 같이 정책을 설정한다.
  • 참고로, Resource 부분의 맨 끝에는 /*을 달아줘야 데이터에 접근할 수 있다.

application 파일 설정

  • 스프링 부트에서 사용될 S3의 정보를 설정한다.
  • 엑세스 키와 시크릿 키는 환경 변수를 통해 설정하였다.
    • 즉, 프로그램을 실행시킬 때 환경 변수를 설정하여 실행시키면 자동으로 application 파일에 맵핑이 된다.

Configuration 클래스 생성

  • 서비스 클래스에서 사용될 AmazonS3 빈을 등록한다.
  • 해당 빈의 구현체는 AmazonS3Client이다.
@Configuration
public class AwsS3Config {

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

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

    @Bean
    public AmazonS3 amazonS3() {
        return AmazonS3ClientBuilder
            .standard()
            .withRegion(Regions.AP_NORTHEAST_2)
            .withCredentials(awsStaticCredentialsProvider())
            .build();
    }

    @Bean
    public AWSStaticCredentialsProvider awsStaticCredentialsProvider() {
        BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(accessKey, secretKey);
        return new AWSStaticCredentialsProvider(basicAWSCredentials);
    }
}

S3로 파일 업로드

  • 컨트롤러로부터 받아온 MultipartFile이 이미지 타입인지 확인한다.
  • 파일 이름을 유저네임_UUID으로 설정한다.
  • ObjectMetadata를 통해 해당 사진의 메타데이터를 생성한다.
  • AmazonS3.putObject() 메소드를 통해 S3 버킷 이름과 파일 이름, 파일 데이터, 메타데이터를 보낸다.
    • 단, 이미 이미지가 존재하면 기존에 있던 파일을 AmazonS3.deleteImage()를 통해 삭제한다.
  • 사용자 데이터의 imagePath 속성에 AmazonS3.getUrl(bucketName, fileName)로 가져온 이미지의 URI를 가져와 저장한다.
@RequiredArgsConstructor
@Service
public class UserImageServiceImpl implements UserImageService {

    private final AmazonS3 amazonS3;
    private final UserRepository userRepository;

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

    private final static String IMAGE_JPG = "image/jpeg";
    private final static String IMAGE_PNG = "image/png";

    @Override
    @Transactional
    public void uploadImage(final MultipartFile multipartFile, final User user) {
        User findUser = userRepository.findById(user.getId())
            .orElseThrow(NotFoundUserException::new);

        if (!isImageType(multipartFile)) {
            throw new NoImageFileException();
        }

        String fileName = findUser.getUsername() + "_" + UUID.randomUUID();

        ObjectMetadata metadata = new ObjectMetadata();
        metadata.setContentLength(multipartFile.getSize());
        metadata.setContentType(multipartFile.getContentType());

        try {
            if (amazonS3.doesObjectExist(bucketName, user.getImageName())) {
                deleteImage(bucketName, user.getImageName());
            }
            amazonS3.putObject(bucketName, fileName, multipartFile.getInputStream(), metadata);
        } catch (IOException e) {
            throw new FailUploadImageException();
        }

        findUser.uploadImage(fileName, amazonS3.getUrl(bucketName, fileName).toString());
    }
}

S3의 파일 삭제

  • S3에 이미지가 존재하는지 AmazonS3.doesObjectExist()를 통해 확인한다.
  • 이미지가 존재한다면 AmazonS3.deleteObject()를 통해 삭제시킨다.
  • 사용자의 데이터도 업데이트시킨다.
    @Override
    @Transactional
    public void deleteImage(final User user) {
        User findUser = userRepository.findById(user.getId())
            .orElseThrow(NotFoundUserException::new);

        if(!amazonS3.doesObjectExist(bucketName, user.getImageName())){
            return;
        }

        deleteImage(bucketName, user.getImageName());

        findUser.deleteImage();
    }

    private void deleteImage(String bucketName, String imageName){
        try {
            amazonS3.deleteObject(bucketName, imageName);
        } catch (Exception e) {
            throw new FailDeleteImageException();
        }
    }

후기

스프링을 통해 S3로 파일을 업로드하는 것이 처음이여서 보안적으로 허술한 부분이 있을 것이다. S3를 생성할 때, 보안 요소를 좀 더 살펴보고 설정해 줄 필요가 있을 것 같다.

특히, ACL이 무엇인지, 정책은 어떠한 종류가 있는지 알아보고 세세하게 설정해주면 더 좋을 듯 하다.

profile
난 Java도 좋고, 다른 것들도 좋아

0개의 댓글