728x90
IAM
S3가 구성되었다면(S3가 없다면? - S3 버킷 생성) 이제 S3에 접근하기 위해서 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