경험/기술

[Spring] [Kotlin] - S3에 파일 업로드/다운로드

2023. 6. 20.
목차
  1. IAM
  2. IAM 계정 생성
  3. 액세스 키 발급
  4. Spring&Kotlin으로 파일 업로드/다운로드
  5. Spring 환경 설정
  6. 기능 구현
  7. 테스트
728x90

IAM

S3가 구성되었다면(S3가 없다면? - S3 버킷 생성) 이제 S3에 접근하기 위해서 IAM 계정을 생성해줘야 한다.

 

IAM 계정 생성

IAM을 검색해서 사용자 선택 후 사용자 추가를 클릭


사용자 세부 정보 지정

  • 사용자 이름은 구분할 수 있게 마음대로 지정하면 된다.
  • 액세스 권한은 선택해도 되고 안 해도 된다. 여기서는 S3를 접근하기 위해 만드는 것이라 선택하지 않았다.

권한 설정

  • 권한은 S3FullAccess권한을 부여했다.
  • 여기서는 직접 연결을 했지만 정책을 그룹에 연결한 후 사용자를 적절한 그룹에 추가하는 것을 권장하고 있다.

검토 및 생성

  • 태그는 굳이 생성하지 않았다. (필요시에 생성하면 됨)

사용자 생성을 누르면 잘 생성되었다. 여기서 이제 애플리케이션에서 S3로 접근하기 위해 필요한 key를 발급받아야 한다.

 

액세스 키 발급

엑세스 키 만들기 클릭

  • 만든 IAM 유저를 들어가서 > 보안 자격 증명 탭에 가보면 > 액세스 키 만들기를 할 수 있다.

액세스 키 모범 사례 및 대안

  • 어떤 모범 사례를 선택해도 액세스 키는 똑같이 발급된다.
  • 그래도 상황에 맞는 대안을 고르면 된다.

설명 태그 설정

  • 설명 태그는 선택 사항이지만, 설명을 적어두면 나중에 쉽게 구별하는데 도움을 줄 수 있다.

액세스 키 생성

이렇게 하면 액세스 키와 시크릿 키를 발급받을 수 있다.

 

Spring&Kotlin으로 파일 업로드/다운로드

이제 스프링에서 파일 업로드와 다운로드를 해보자!

 

Spring 환경 설정

//application.yml

cloud:
  aws:
    region:
      auto: false
      static: ap-northeast-2
    stack:
      auto: false

s3:
  access-key: S3-ACCESSKEY
  secret-key: S3-SECRETKEY
  bucket: S3-BUCKETNAME
// build.gradle

implementation('org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE')
  • access-key와 secret-key에는 IAM 유저를 생성할 때 만들었던 키들을 넣어주면 되고, bucket에는 생성한 s3(ex: domain-web-storage) 이름을 적어주면 된다.

 

import com.amazonaws.auth.AWSStaticCredentialsProvider
import com.amazonaws.auth.BasicAWSCredentials
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
class AwsConfig(
    @Value("\${s3.access-key}") private val accessKey: String,
    @Value("\${s3.secret-key}") private val secretKey: String,
    @Value("\${cloud.aws.region.static}") private val region: String
) {
    @Bean
    fun amazonS3Client(): AmazonS3Client? = AmazonS3ClientBuilder.standard()
        .withRegion(region)
        .withCredentials(
            AWSStaticCredentialsProvider(
                BasicAWSCredentials(
                    accessKey,
                    secretKey
                )
            )
        )
        .build() as AmazonS3Client
}
  • S3에 접근하기 위한 Client를 만들어서 빈에 등록해 준다.

 

기능 구현

import org.springframework.http.ResponseEntity
import org.springframework.web.multipart.MultipartFile

interface S3Service {

    /** S3 파일 업로드 **/
    fun upload(folder: String, multipartFileList: Array<MultipartFile>): List<String>

    /** S3 파일 다운로드 **/
    fun download(folder: String, fileKey: String): ResponseEntity<ByteArray>
}
  • 우선 인터페이스로 역할을 만들어준다.

 

import com.amazonaws.services.s3.AmazonS3Client
import com.amazonaws.services.s3.model.CannedAccessControlList
import com.amazonaws.services.s3.model.GetObjectRequest
import com.amazonaws.services.s3.model.ObjectMetadata
import com.amazonaws.services.s3.model.PutObjectRequest
import com.amazonaws.util.IOUtils
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Service
import org.springframework.web.multipart.MultipartFile
import java.net.URLEncoder

@Service
class S3ServiceImpl(
    private val amazonS3Client: AmazonS3Client,
    @Value("\${s3.bucket}") private val s3Bucket: String
) : S3Service {

    // 파일들을 Amazon S3에 업로드하는 함수
    override fun upload(folder: String, multipartFileList: Array<MultipartFile>): List<String> {
        val imagePathList = mutableListOf<String>()

        multipartFileList.forEach { multipartFile ->
            // 업로드할 파일의 이름 및 크기를 설정
            val originalName = folder + "/" + multipartFile.originalFilename 
            val size = multipartFile.size 

            // 파일 메타데이터 설정
            val objectMetaData = ObjectMetadata().apply {
                contentType = multipartFile.contentType
                contentLength = size
            }

            // Amazon S3에 파일 업로드
            amazonS3Client.putObject(
                PutObjectRequest(s3Bucket, originalName, multipartFile.inputStream, objectMetaData)
                    .withCannedAcl(CannedAccessControlList.PublicRead)
            )

            // 업로드된 파일의 URL을 가져와서 리스트에 추가
            val imagePath = amazonS3Client.getUrl(s3Bucket, originalName).toString() 
            imagePathList.add(imagePath)
        }

        // 업로드된 파일들의 URL 리스트를 반환
        return imagePathList
    }

    // Amazon S3에서 파일을 다운로드하는 함수
    override fun download(folder: String, fileKey: String): ResponseEntity<ByteArray> {
        // 파일의 S3 object를 가져옴
        val s3Object = amazonS3Client.getObject(GetObjectRequest(s3Bucket, "$folder/$fileKey"))
        val objectInputStream = s3Object.objectContent
        // 파일 내용을 byte 배열로 변환
        val bytes: ByteArray = IOUtils.toByteArray(objectInputStream)

        // 파일 이름 인코딩
        val fileName: String = URLEncoder.encode(fileKey, "UTF-8").replace("+", "%20")
        val httpHeaders: HttpHeaders = HttpHeaders().apply {
            // 헤더 설정
            contentType = MediaType.APPLICATION_OCTET_STREAM
            contentLength = bytes.size.toLong()
            setContentDispositionFormData("attachment", fileName)
        }

        // 파일 내용과 헤더를 가지고 응답을 반환
        return ResponseEntity(bytes, httpHeaders, HttpStatus.OK)
    }
}

 

Controller 구현

import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import org.springframework.web.multipart.MultipartFile
import @@.@@.s3.S3Service // 맞는 경로로 수정

@RestController
@RequestMapping("s3")
class S3Controller(
    private val s3Service: S3Service
) {

    /** S3 업로드 API**/
    @PostMapping("/upload")
    fun upload(
        @RequestParam("folder") folder: String,
        @RequestParam("fileList") fileList: Array<MultipartFile>
    ): ResponseEntity<Unit> {
        s3Service.upload(folder, fileList)
        return ResponseEntity.ok().build()
    }

    /** S3 다운로드 API **/
    @GetMapping("/download")
    fun download(@RequestParam folder: String, fileKey: String) =
        s3Service.download(folder, fileKey)
}

 

테스트

  • 업로드에서 fileList는 type을 File로 하면 파일을 올릴 수 있다. 여러 개를 선택하고 싶을 때는 여러 개를 select 하면 된다.

  • 다운로드는 그냥 Send 하게 되면 알 수 없게 나오는데 Send and Download를 하면 직접 다운이 되는지 안 되는지 확인할 수 있다.

 

더 자세히 알아볼 수 있는 참고 자료: https://techblog.woowahan.com/11392/

 

 

 

요번에는 S3에 파일올리기한 경험을 기반으로 블로그에 작성해 봤는데 아직 설명도 깔끔하지 않고 부족한 점이 많았습니다. 잘 안 되는 부분이나 오류가 나는 부분이 있으면 댓글로 공유 부탁드려요! 언제나 잘못된 설명이나 부족한 부분에 대한 피드백은 환영입니다🤍

더보기

사실 fileKey를 암호화해서 저장하는 방식을 했었는데 의존성 주입도 해야 하고 복잡해져서 제외하고 했는데 요청이 있다면 그것도 추가해서 올려보겠습니다.

728x90
저작자표시 비영리 변경금지 (새창열림)
  1. IAM
  2. IAM 계정 생성
  3. 액세스 키 발급
  4. Spring&Kotlin으로 파일 업로드/다운로드
  5. Spring 환경 설정
  6. 기능 구현
  7. 테스트
'경험/기술' 카테고리의 다른 글
  • [S3] [CloudFront] [Route 53] CDN 구축기 - 무료 이미지 서버, 기존 시스템의 이해
  • [Spring] [Kotlin] [MongoDB] 엑셀 다운로드
  • [Kotlin] let, run, apply, also, with 파헤치기
  • [S3] AWS S3 버킷 생성
호야_
호야_
안녕하세요😊 아는 것만 보이는 게 아닌, 아는 만큼 보인다고 생각하는 백엔드 개발자입니다.
호야_
개발잠
호야_
전체
오늘
어제
  • 분류 전체보기 (28)
    • 경험 (28)
      • 이슈 (12)
      • 기술 (11)
      • 회고 (5)

블로그 메뉴

  • 깃허브
  • 블로그관리 홈
  • 글쓰기

인기 글

최근 글

최근 댓글

250x250
hELLO · Designed By 정상우.
호야_
[Spring] [Kotlin] - S3에 파일 업로드/다운로드
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.