본문 바로가기
공부/AWS

[AWS] 스프링에서 S3 버킷에 이미지 업로드하기

by 웅대 2023. 2. 24.
728x90
반응형

이전 포스팅에서는 관리자 IAM 사용자를 만들어보았다.

https://growth-coder.tistory.com/115

 

이번 포스팅에서는 스프링에서 S3에 이미지를 업로드 하기 위한 IAM 사용자 생성과 코드를 통해 S3 버킷에 이미지를 업롣르해보려한다.

 

IAM 사용자 생성

 

먼저 만들어둔 관리자 IAM 사용자로 로그인을 한 뒤 버킷을 생성하고 버킷 정책까지 설정한다.

 

https://growth-coder.tistory.com/114

 

[AWS] Amazon S3 개념 및 파일 업로드 해보기

Amazon S3는 Amazon Simple Storage Service의 약자로 데이터를 객체 단위로 관리하는 오브젝트 스토리지 서비스이다. 가끔 프로젝트를 진행하다보면 이미지나 동영상 같은 파일들을 서버에 업로드하는 기

growth-coder.tistory.com

위 포스팅대로 진행하면 되는데 다른 점은 실습을 위해 퍼블릭 액세스 제한을 해제하고 ACL을 활성화 해두는 것이다.

 

이제 IAM 사용자를 만들 차례이다.

IAM -> 사용자 -> 사용자 추가

console 사용자 액세스 권한 제공 -> IAM 사용자를 생성하고 싶음 -> 나머지는 기본 설정 -> 다음

권한 옵션 -> 직접 정책 연결 -> AmazonS3FullAccess 선택 -> 다음

이상 없다면 사용자 생성을 누른다.

 

IAM -> 사용자 -> 보안 자격 증명 -> 액세스 키 만들기

선택 후 다음

액세스 키 만들기

 

.csv 파일로 다운 받아 외부에 공개하지 않도록 하자.

 

스프링 코드

 

스프링의 빌드 관리 도구로는 gradle을 사용했다.

 

먼저 dependency를 추가해준다.

 

dependencies {
.
.
.
   implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
}

 

키, 버킷 이름, 리전의 경우 main / resources / application.properties에 보관하여 외부에 노출되지 않도록 한다.

 

<application.properties>

#access key
cloud.aws.credentials.accessKey=ACCESS_KEY

#secret key
cloud.aws.credentials.secretKey=SECRET_KEY

#버킷 이름
cloud.aws.s3.bucketName=BUCKET_NAME

#리전
cloud.aws.region.static=ap-northeast-2

#cloud formation 기능을 사용하지 않기 위함.
cloud.aws.stack.auto=false

S3에 접근하기 위해서 AmazonS3Client를 사용할 예정이므로 기본 설정을 하여 스프링 빈으로 등록해준다.

 

<S3Config>

@Configuration
public class S3Config {
    @Value("cloud.aws.credentials.accessKey")
    private String accessKey;
    @Value("cloud.aws.credentials.secretKey")
    private String secretKey;
    @Value("cloud.aws.s3.bucketName")
    private String bucketName;
    @Value("cloud.aws.region.static")
    private String region;

    @Bean
    public AmazonS3 s3Builder() {
        AWSCredentials basicAWSCredentials = new BasicAWSCredentials(accessKey, secretKey);

        return AmazonS3ClientBuilder.standard()
                .withCredentials(new AWSStaticCredentialsProvider(basicAWSCredentials))
                .withRegion(region).build();
    }
}

 

스프링 빈으로 등록한 AmazonS3 객체를 사용하여 이미지 파일을 S3 업로드할 수 있다.

 

AmazonS3의 메소드는 다음 링크에서 확인할 수 있다.

https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/s3/AmazonS3.html

 

AmazonS3 (AWS SDK for Java - 1.12.410)

This action filters the contents of an Amazon S3 object based on a simple structured query language (SQL) statement. In the request, along with the SQL expression, you must also specify a data serialization format (JSON, CSV, or Apache Parquet) of the obje

docs.aws.amazon.com

 

파일 업로드를 하기 위해선 AmazonS3의 putObject 메소드를 사용해야한다.

 

여러 파라미터들이 존재하는데 가장 위에 있는 PutObjectRequest를 사용하여 업로드 할 예정이다.

 

PutObjectRequest도 생성하는 방법이 여러개다.

.
.
.
public PutObjectRequest(String bucketName, String key, File file) {
    super(bucketName, key, file);
}

public PutObjectRequest(String bucketName, String key, String redirectLocation) {
    super(bucketName, key, redirectLocation);
}

public PutObjectRequest(String bucketName, String key, InputStream input,
        ObjectMetadata metadata) {
    super(bucketName, key, input, metadata);
}
.
.
.

bucketName : 객체를 올릴 버킷 일므

key : 파일명

input : InputStream

metadata : 메타데이

 

이 중 InputStream을 사용하는 마지막 방법을 사용하려고 한다.

 

이미지를 업로드하여 로컬에다 저장하는 비즈니스 로직은 이전에 만들었던 적이 있다.

https://growth-coder.tistory.com/104

 

[Spring] JSON과 같이 이미지 요청 받아서 저장

간단한 게시판을 만드던 중 이미지 업로드하는 기능이 필요해서 만들어보았다. 1. 이미지 이름 이미지를 보내서 저장할 때는 이미지의 이름이 중요하다. 같은 이름을 가진 이미지를 전송할 경우

growth-coder.tistory.com

위 포스팅에서 만들었던 로직을 조금 변경해서 사용할 예정이다.

 

변경할 내용은 다음과 같다.

 

  1. 서비스로 만들어서 스프링 빈에 등록
  2. 로컬이 아닌 S3에 이미지 저장하기

이미지 업로드 로직을 간단하게 요약하면

 

  1. 이미지가 MultiPartFile 형식으로 들어오면 랜덤으로 UUID를 생성해서 이미지 이름 앞에 붙인다. (이미지 이름 중복 방지)
  2. 이미지를 s3 버킷에 업로드하고 해당 url을 받아서 원본 이미지 이름과 함께 데이터베이스에 저장한다.

즉 이미지는 S3에 업로드되고 데이터베이스에는 그 이미지의 주소가 저장되어 데이터베이스의 부담을 줄이는 방식이다.

 

putObject는 파라미터로 버킷 이름이 필요하고 버킷 이름은 application.properties에 있으므로 @Value 어노테이션을 사용하여 가져온다.

@Value("${cloud.aws.s3.bucketName}")
private String bucketName; //버킷 이름
private final AmazonS3 amazonS3;
먼저 원본 이미지 이름에 붙일 랜덤 UUID를 생성하는 메소드를 만든다.
private String changedImageName(String originName) { //이미지 이름 중복 방지를 위해 랜덤으로 생성
    String random = UUID.randomUUID().toString();
    return random+originName;
}

이제 이미지를 s3에 업로드하는 메소드를 만들 차례이다.

private String uploadImageToS3(MultipartFile image) { //이미지를 S3에 업로드하고 이미지의 url을 반환
    String originName = image.getOriginalFilename(); //원본 이미지 이름
    String ext = originName.substring(originName.lastIndexOf(".")); //확장자
    String changedName = changedImageName(originName); //새로 생성된 이미지 이름
    ObjectMetadata metadata = new ObjectMetadata(); //메타데이터
    metadata.setContentType("image/"+ext);
    try {
        PutObjectResult putObjectResult = amazonS3.putObject(new PutObjectRequest(
                bucketName, changedName, image.getInputStream(), metadata
        ).withCannedAcl(CannedAccessControlList.PublicRead));

    } catch (IOException e) {
        throw new ImageUploadException(); //커스텀 예외 던짐.
    }
    return amazonS3.getUrl(bucketName, changedName).toString(); //데이터베이스에 저장할 이미지가 저장된 주소

}

한 줄씩 살펴보자면 MultiPartFile 형식으로 image가 들어오면 

String originName = image.getOriginalFilename(); //원본 이미지 이름
String ext = originName.substring(originName.lastIndexOf(".")); //확장자
String changedName = changedImageName(originName); //새로 생성된 이미지 이름

원본 이미지 이름과 확장자를 뽑아내고 새로운 이미지 이름을 생성한다.

 

ObjectMetadata metadata = new ObjectMetadata(); //메타데이터
metadata.setContentType("image/"+ext);

putObject의 인자로 들어갈 메타데이터를 생성해준다. 

 

다른 파일말고 이미지만 받을 예정이므로 contentType은 "image/확장자"로 정한다.

 

이미지 뿐만 아니라 파일도 받는다면 파일 형식에 맞게끔 contentType을 다 지정해줘야한다.

try {
    PutObjectResult putObjectResult = amazonS3.putObject(new PutObjectRequest(
            bucketName, changedName, image.getInputStream(), metadata
    ).withCannedAcl(CannedAccessControlList.PublicRead));

} catch (IOException e) {
    throw new ImageUploadException(); //커스텀 예외 던짐.
}

putObject의 인자로 버킷 이름, 새로 생성된 이미지 이름, 인풋 스트림, 메타데이터를 차례대로 넣어준다.

 

IOException이 발생하면 ImageUploadException이라는 커스텀 예외를 던진다.

 

ImageUploadException은 RunTimeException을 상속받아 직접 만든 예외이다.

 

이렇게 업로드에 성공했으면 AmazonS3의 getUrl 메소드를 사용하여 저장된 url을 반환한다.

return amazonS3.getUrl(bucketName, changedName).toString(); //데이터베이스에 저장할 이미지가 저장된 주소

 

이러한 정보들을 바탕으로 데이터베이스에 저장할 Image 객체를 생성하여 반환한다.

public Image uploadImage(MultipartFile image, Post post){
	//데이터베이스에 저장할 엔티티 반환
    String originName = image.getOriginalFilename();
    String storedImagePath = uploadImageToS3(image);

    Image newImage = Image.builder() //이미지에 대한 정보를 담아서 반환
            .originName(originName)
            .storedImagePath(storedImagePath)
            .post(post).build();
    return newImage;
}

 

<ImageS3Service 최종 코드>

@Service
@RequiredArgsConstructor
public class ImageS3Service{
    private final AmazonS3 amazonS3;
    @Value("${cloud.aws.s3.bucketName}")
    private String bucketName; //버킷 이름
    private String changedImageName(String originName) { //이미지 이름 중복 방지를 위해 랜덤으로 생성
        String random = UUID.randomUUID().toString();
        return random+originName;
    }

    private String uploadImageToS3(MultipartFile image) { //이미지를 S3에 업로드하고 이미지의 url을 반환
        String originName = image.getOriginalFilename(); //원본 이미지 이름
        String ext = originName.substring(originName.lastIndexOf(".")); //확장자
        String changedName = changedImageName(originName); //새로 생성된 이미지 이름
        ObjectMetadata metadata = new ObjectMetadata(); //메타데이터
        metadata.setContentType("image/"+ext);
        try {
            PutObjectResult putObjectResult = amazonS3.putObject(new PutObjectRequest(
                    bucketName, changedName, image.getInputStream(), metadata
            ).withCannedAcl(CannedAccessControlList.PublicRead));

        } catch (IOException e) {
            throw new ImageUploadException(); //커스텀 예외 던짐.
        }
        return amazonS3.getUrl(bucketName, changedName).toString(); //데이터베이스에 저장할 이미지가 저장된 주소

    }


    public Image uploadImage(MultipartFile image, Post post){
        String originName = image.getOriginalFilename();
        String storedImagePath = uploadImageToS3(image);

        Image newImage = Image.builder() //이미지에 대한 정보를 담아서 반환
                .originName(originName)
                .storedImagePath(storedImagePath)
                .post(post).build();
        return newImage;
    }

}

 

깃허브 : https://github.com/ezcolin2/Spring-Class/tree/main/image%20upload/with%20s3

 

GitHub - ezcolin2/Spring-Class: 스프링 클래스

스프링 클래스. Contribute to ezcolin2/Spring-Class development by creating an account on GitHub.

github.com

 

참고

 

https://velog.io/@rainbowweb/AWS-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-S3

https://docs.aws.amazon.com/ko_kr/sdk-for-java/v1/developer-guide/examples-s3-objects.html

https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/s3/AmazonS3.html

 

 

 

 

728x90
반응형

댓글