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

[Dev] 23.04.05. AWS S3 Bucket으로의 업로드 (with. Access Key)

by 규글 2023. 4. 5.

 많은 블로그에서 확인할 수 있던 S3 Bucket으로의 업로드에는 access key와 secret key라는 친구들이 활용되었다. 하지만 필자가 S3 Bucket을 생성할 때도 해당 key들은 만들어지지 않았으며, key의 정보가 담긴 csv file도 얻을 수 없었다. Bucket을 생성한 후에 key를 생성하는 방법을 알게 되었으나, 해당 과정에서는 key를 생성하는 것을 권장하지 않고 있어서 조금은 당황했다.

 그래도 key를 생성하는 것이 불가능한 것은 아니기 때문에, 우선 key를 생성하여 이를 활용한 S3로의 업로드 기능을 작업해보려고 한다. 그동안 많은 블로그에서 다룰 정도로 폭넓게 알려져 있으므로 일단 경험해보고, 왜 이 방법을 권장하지 않는지 알아본 뒤 AWS에서 권장하는 방식으로 변경해볼 생각이다.

 

 

Access Key 생성하기

 가장 먼저 IAM 서비스 페이지에서 사용자 > 보안 자격 증명 탭에는 액세스 키에 대한 항목이 있다. 액세스 키 만들기를 누른다.

 

 액세스 키 만들기를 누르면 액세스 키 모범 사례 및 대안으로 선택창이 등장한다. 각각을 선택하면 그에 맞는 권장하는 대안에 대한 정보를 제공하는데, 필자의 경우 AWS EC2에 업로드하여 구동하는 것이 목적이므로 AWS 컴퓨팅 서비스에서 실행되는 애플리케이션을  선택해주었다. 권장하는 대안이 존재하나, 일단은 실습을 위해서 액세스 키 생성을 계속하기로 했다.

 

 태그 설정은 선택사항이므로 아무 것도 작성하지 않은 채로 액세스 키를 만들었다.

 

 액세스 키를 성공적으로 생성하였고, 그에 대한 csv 파일을 다운로드 할 수 있다.

 

 

프로젝트에서 S3 업로드 작업

 작업은 footnote의 블로그를 참고했다.[각주:1] [각주:2]

 

build.gradle

// https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-aws
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-aws', version: '2.2.6.RELEASE'

 build.gradle file에 spring cloud starter aws에 대한 dependency를 추가해준다. 현재 글을 작성하고 있는 시점에서 2.2.6 version 임을 mvn repository에서 확인하였으며, 이는 참고한 블로그에 작성된 것과도 동일하다.

 

application-aws-s3.properties

#AWS S3 관련 설정
cloud.aws.credentials.access-key = blahblah
cloud.aws.credentials.secret-key = blahblahblahblah
cloud.aws.s3.bucket = bucketname
cloud.aws.region.static = ap-northeast-2
cloud.aws.stack.auto = false

 위 다섯 항목에 대한 값을 입력해준다. Access key와 secret key는 IAM 사용자의 key 정보를 입력하면 되고, bucket 정보에는 생성한 bucket 이름을 입력하면 된다. Region 또한 bucket의 region 정보를 입력해주면 되겠다.

 

application.properties

# properties including
spring.profiles.include = aws-s3

 그리고 위에서 작성했던 properties file의 항목들을 직접 그대로 작성하는 것이 아니라 application.properties에 포함시키는 방식으로 작성해준다. 이렇게 작성해도 application-aws-s3.properties에 작성한 내용까지도 적용할 수 있다. 이렇게 따로 작성한 이유는 다음의 .gitignore에서 application-aws-s3.properties를 ignore 하기 위함이다.

 이때의 naming rule 은 'application-{profile name}.properties' 로, profile name 자리에 해당하는 값을 spring.profiles.include 항목에 작성해주면 되겠다.

 

 이 방식은 spring boot 2.4 이상에서 deprecated 되어 일부 상황에서는 사용할 수 없다고 하는데, 이에 관해서는 추후 다른 게시글로 정리해보겠다.

 

.gitignore

### AWS properties
application-*.properties

 따로 IAM의 access key와 secret key를 작성한 properties file의 경우에는 외부에 노출되면 안되기 때문에 gitignore 설정을 하는 것으로 보인다.

 그렇다면 현재 동작은 원활하게 되더라도, 현재의 방식은 EC2 인스턴스에 github와 연동하여 build 하게 되면 해당 파일이 무시되어 온전하게 동작하지 못하게 될 것으로 추정할 수 있다. 따라서 현재의 방식은 또다시 수정되어야만 할 것이다. 또다른 방법을 찾는 것은 잠시 미뤄두고, 현재의 방식으로 local에서 구현해보기 위해 계속 작업해보겠다.

 

AwsS3Config.java

package com.example.recipository.config;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@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 AmazonS3 amazonS3Client(){
        BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);

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

 Properties file에 작성했던 access key, secret key, region 정보를 바탕으로 AmazonS3 를 Bean으로 만들 수 있도록 Configuration file을 추가했다. 이때의 AmazonS3Client는 AmazonS3를 implements 한 객체이다.

 

RecipeServiceImpl.java

    @Value("${cloud.aws.s3.bucket}")
    private String bucket;
    private final AmazonS3Client amazonS3Client;
    
    // 게시글을 작성하는 service logic
    @Transactional
    @Override
    public boolean write(RecipeDto recipeDto,
                         MultipartFile imageFile,
                         Member member) {
        try(InputStream inputStream = imageFile.getInputStream()) {
            if (!imageFile.isEmpty()) {
                // 넘겨받은 file name
                String originFileName = imageFile.getOriginalFilename();
                // save file name에 사용할 UUID String
                String uuid = UUID.randomUUID().toString();
                // save file name
                String saveFileName = uuid + originFileName;

                ObjectMetadata objectMetadata = new ObjectMetadata();
                objectMetadata.setContentLength(imageFile.getSize());
                objectMetadata.setContentType(imageFile.getContentType());

                // S3 bucket에 saveFileName으로 저장
                amazonS3Client.putObject(
                        new PutObjectRequest(bucket, saveFileName, inputStream, objectMetadata)
                );

                // 방금 file이 저장된 url
                String storeFileUrl = amazonS3Client.getUrl(bucket, saveFileName).toString();

                // RecipeDto에 image url save
                recipeDto.setImagePath(storeFileUrl);
            }
            ...
            (후략)

 마찬가지로 bucket 정보를 받고, AmazonS3 의 putObject method를 통해서 업로드할 수 있도록 작성했다. AmazonS3Client의 putObject method를 통해 S3 bucket으로 file을 upload 할 수 있는데, 이때 PutObjectRequest를 인자로 전달한다. 이 PutObjectRequest 또한 bucket 이름과 저장할 file의 이름, InputStream과 ObjectMetaData를 인자로 필요로 하기 때문에 전달해준다.

 마지막으로 client에서 html image의 src 속성에 들어갈 imagePath에 정보를 넣어주기 위해서 AmazonS3Client의 getUrl method를 사용했다. 바로 위에서 저장한 bucket 정보와 file 이름을 인자로하여 저장된 이미지의 url 정보를 얻어낼 수 있다.

 

 

 업로드는 이것으로 순조롭게 마쳤다고 할 수 있고, 파일이 업로드되는 것을 직접 확인까지 하였다. 하지만 기존의 버킷 설정 그대로 둔 상태, 즉 버킷이 퍼블릭하지 않다면 업로드한 파일을 조회하는 것은 불가능하다. 바로 다음 게시글에서 해당 설정을 다시 해줄 것이다.

 

 

Reference

댓글