SDk2.x 버전을 사용하는 자료가 많지 않아 직접 공식 문서를 참고해 기능을 만들었습니다.
- AWS S3 Bucket을 생성한다.
- Bucket 설정을 한다.
- 퍼블릭 액세스를 차단한다. (반드시 버킷 설정 이후에 해야함, 에러가 발생함)
- S3 권한을 가진 IAM 사용자를 만들고, 액세스 키와 시크릿 키을 생성한다.
val awsVersion = "2.25.23"
implementation("software.amazon.awssdk:s3:$awsVersion")
amazon:
aws:
access-key: "${AMAZON_AWS_ACCESS_KEY:your-access-key}"
secret-key: "${AMAZON_AWS_SECRET_KEY:your-secret-key}"
region: "${AMAZON_AWS_REGION:your-region}"
bucket: "${AMAZON_AWS_BUCKET:your-bucket-name}"
이 정보는 유출 되지 않게 주의하자
@Configuration
class AwsS3Config(
@Value("\${amazon.aws.access-key}") private val accessKey: String,
@Value("\${amazon.aws.secret-key}") private val secretKey: String
) {
@Bean
@Primary
fun awsCredentials(): AwsCredentials {
return AwsBasicCredentials.create(accessKey, secretKey)
}
@Bean
fun credentialsProvider(): AwsCredentialsProvider {
return AwsCredentialsProvider { awsCredentials() }
}
}
presigned-Url 생성은 sdk1.x 버전과 다르게 S3Presigner를 사용하는데, S3Presigner를 생성할 때 credentialsProvider가 필요하다.
@Service
class FileService(
@Value("\${amazon.aws.bucket}") private val bucketName: String,
@Value("\${amazon.aws.region}") private val region: String,
private val awsCredentialsProvider: AwsCredentialsProvider,
) {
companion object {
private const val DURATION_OF_PRESIGNED_URL_MINUTE = 30L
}
suspend fun generatePreSignedUrlToUpload(fileName: String, fileDomain: String): FileUrlResponseDto {
val encodedFileName = "${createUUID()}_${fileName}"
val objectName = "${LocalDate.now()}/$fileDomain/$encodedFileName"
S3Presigner.builder()
.credentialsProvider(awsCredentialsProvider)
.region(Region.of(region))
.build().use { presigner ->
PutObjectPresignRequest.builder()
.signatureDuration(Duration.ofMinutes(DURATION_OF_PRESIGNED_URL_MINUTE))
.putObjectRequest(
PutObjectRequest.builder()
.bucket(bucketName)
.key(objectName)
.build()
)
.build().run {
return FileUrlResponseDto.from(presigner.presignPutObject(this).url().toExternalForm())
}
}
}
/**
* 파일 고유 ID를 생성
* @return 36자리의 UUID
*/
private suspend fun createUUID() = UUID.randomUUID().toString()
}
- 먼저 파일 이름앞에 UUID를 Prefix로 붙인다.
- ObjectName의 "/"는 파일의 path를 의미하는데, YYYY-MM-DD/{domainName}/{fileName}의 형태로 버킷에 저장된다.
- S3Presigner는 사용 후 Close를 해줘야 하는데, 코틀린의 use(try-with-resources)를 사용 하였다.
- PresignedUrl 생성은 네트워크 통신을 하지 않기 때문에 withContext(Dispathcer)가 필요없다.
- 업로드는 Put 권한을 가진 Url을 반환한다.
suspend fun generatePreSignedUrlToDownload(objectName: String): FileUrlResponseDto {
S3Presigner.builder()
.credentialsProvider(awsCredentialsProvider)
.region(Region.of(region))
.build().use { presigner ->
GetObjectPresignRequest.builder()
.signatureDuration(Duration.ofHours(DURATION_OF_PRESIGNED_URL_MINUTE))
.getObjectRequest(
GetObjectRequest.builder()
.bucket(bucketName)
.key(objectName)
.build()
)
.build().run {
return FileUrlResponseDto.from(presigner.presignGetObject(this).url().toExternalForm())
}
}
}
다운로드는 Path를 포함한 ObjectName을 아규먼트로 받아서 Get 권한을 가진 Url을 반환한다.
포스트 맨으로 테스트해보자
위와 같이 요청을 보내면
응답이 오는데 이 응답 주소를 Put 메서드로 아래와 같이 하게 되면
S3에 정상적으로 업로드 되었다.
업로드 된 파일의 객체 URL을 접속하면 아래와 같이 나타나는데, 이는 퍼블릭 액세스를 차단했기 때문이다.
마지막으로, 파일을 다운로드 해보자
이제 위 주소를 Get Method로 호출하면
저장했던 사진이 정상적으로 나오는 것을 확인 할 수 있다.
https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/examples-s3-presign.html
https://github.com/aws/aws-sdk-java-v2/blob/master/docs/LaunchChangelog.md#411-s3-operation-migration
잘 보고 갑니다.