티스토리 뷰

개요

  • Amazon S3는 오브젝트 저장소 상품이다. 이번 글에서는 Spring Boot 기반 애플리케이션에서 Amazon S3에 파일을 업로드하고 다운로드하는 방법을 설명하고자 한다.

사전 조건

  • IAM 콘솔에서 새로운 정책을 생성하고, 버킷에 접근 가능한 권한 목록을 부여한다.
  • 생성한 정책을 특정 그룹에 적용한다.
  • 애플리케이션에서 사용할 사용자 계정을 생성하고 해당 그룹에 추가한다.
  • 애플리케이션에서 접근할 버킷을 생성한다. 버킷 정책에 아래 정보를 입력한다.

build.gradle

  • 프로젝트 루트의 /build.gradle 파일에 아래 내용을 추가한다.
dependencies {
    compile group: 'com.amazonaws', name: 'aws-java-sdk', version: '1.11.820'
    compile group: 'com.amazonaws', name: 'aws-java-sdk-s3', version: '1.11.820'
    compile group: 'commons-io', name: 'commons-io', version: '2.6'
}

application.yml

  • 프로젝트 루트의 /src/main/resources/application.yml 파일에 아래 내용을 추가한다.
amazon:
  s3:
    access-key: {access-key}
    secret-key: {secret-key}
  • 앞서 사전 조건에 명시된 사용자 계정 정보를 입력할 차례이다. IAM 콘솔에서 생성한 사용자 계정에 할당된 액세스 키 IDaccess-key 항목에, 비밀 액세스 키secret-key 항목에 입력한다. 잘못된 값를 입력할 경우 Amazon S3 접근시 Access Denied 오류가 발생할 수 있다.

AwsConfig.kt

  • Amazon S3 접속에 필요한 오브젝트를 싱글턴 빈으로 초기화하는 @Configuration 클래스를 아래와 같이 작성한다.
import com.amazonaws.auth.AWSCredentials
import com.amazonaws.auth.AWSStaticCredentialsProvider
import com.amazonaws.auth.BasicAWSCredentials
import com.amazonaws.regions.Regions
import com.amazonaws.services.s3.AmazonS3
import com.amazonaws.services.s3.AmazonS3ClientBuilder
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class AwsConfig(

        @Value("\${amazon.s3.access-key}")
        val accessKey: String,

        @Value("\${amazon.s3.secret-key}")
        val secretKey: String
) {

    @Bean
    fun awsCredentials(): AWSCredentials {

        return BasicAWSCredentials(accessKey, secretKey)
    }

    @Bean
    fun amazonS3(@Qualifier("awsCredentials") awsCredentials: AWSCredentials): AmazonS3 {

        return AmazonS3ClientBuilder
                .standard()
                .withCredentials(AWSStaticCredentialsProvider(awsCredentials))
                .withRegion(Regions.AP_NORTHEAST_2)
                .build()
    }
}

StorageService.kt

  • Amazon S3 업로드 및 다운로드를 수행하는 서비스 인터페이스를 아래와 같이 작성한다.
import java.io.File

interface StorageService {

    fun upload(bucket: String, key: String, file: File)
    fun download(bucket: String, key: String): File
}

StorageServiceImpl.kt

  • 마지막이다. Amazon S3 업로드 및 다운로드를 수행하는 서비스 구현체를 아래와 같이 작성한다.
import com.amazonaws.services.s3.AmazonS3
import com.amazonaws.services.s3.model.AmazonS3Exception
import com.amazonaws.services.s3.model.S3Object
import com.amazonaws.services.s3.model.S3ObjectInputStream
import com.funnc.shop.api.common.exception.ShopApiErrorCode
import com.funnc.shop.api.common.exception.ShopApiException
import com.funnc.shop.api.common.logging.GraylogHelper
import org.apache.commons.io.FileUtils
import org.apache.commons.io.FilenameUtils
import org.springframework.stereotype.Service
import java.io.File
import java.io.FileNotFoundException

@Service
class AmazonS3StorageServiceImpl(

        val amazonS3: AmazonS3

) : StorageService {

    override fun upload(bucket: String, key: String, file: File) {

        try {
            amazonS3.putObject(bucket, key, file)
            logger.info("Amazon S3 파일 업로드 성공, [uploadFile=$bucket$key]")
        } catch (ex: AmazonS3Exception) {
            when (ex.errorCode) {
                "NoSuchBucket" -> logger.error("Amazon S3 파일 업로드 실패, 버킷이 존재하지 않음")
                else -> logger.error("Amazon S3 파일 업로드 실패 [errorCode=${ex.errorCode}]")
            }
        } catch (ex: Exception) {
            when (ex.cause) {
                is FileNotFoundException -> logger.error("Amazon S3 파일 업로드 실패, 원본 파일 존재하지 않음")
                else -> logger.error("Amazon S3 파일 업로드 실패, 원인 미상")
            }
            throw SomeApiException(SomeApiErrorCode.FILE_UPLOAD_FAILED)
        }
    }

    override fun download(bucket: String, key: String): File {

        val downloadFilePath = "${System.getProperty("java.io.tmpdir")}/${FilenameUtils.getName(key)}"
        val downloadFile = File(downloadFilePath)

        try {
            val s3object: S3Object = amazonS3.getObject(bucket, key)
            val inputStream: S3ObjectInputStream = s3object.objectContent
            FileUtils.copyInputStreamToFile(inputStream, downloadFile)
        } catch (ex: AmazonS3Exception) {
            when (ex.errorCode) {
                "NoSuchBucket" -> logger.error("Amazon S3 파일 다운로드 실패, 버킷이 존재하지 않음")
                else -> logger.error("Amazon S3 파일 다운로드 실패 [errorCode=${ex.errorCode}]")
            }
        } catch (ex: Exception) {
            logger.error("Amazon S3 파일 다운로드 실패, 원인 미상")
            throw SomeApiException(SomeApiErrorCode.FILE_DOWNLOAD_FAILED)
        }

        logger.info("Amazon S3 파일 다운로드 성공, [downloadFile=$bucket$key]")

        return downloadFile
    }
}

참고 글

댓글
댓글쓰기 폼