퍼블릭 액세스 차단 S3 presigned-Url 업로드, 다운로드 With SDK 2.x and Kotlin

TaeHye0n·2024년 4월 7일
1

pmeet

목록 보기
4/5
post-thumbnail
post-custom-banner

SDk2.x 버전을 사용하는 자료가 많지 않아 직접 공식 문서를 참고해 기능을 만들었습니다.


선행

  1. AWS S3 Bucket을 생성한다.
  2. Bucket 설정을 한다.
  3. 퍼블릭 액세스를 차단한다. (반드시 버킷 설정 이후에 해야함, 에러가 발생함)
  4. S3 권한을 가진 IAM 사용자를 만들고, 액세스 키와 시크릿 키을 생성한다.

1. AWS SDK 의존성 추가

  val awsVersion = "2.25.23"
  implementation("software.amazon.awssdk:s3:$awsVersion")

2.yml 파일에 aws 정보 작성

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}"

이 정보는 유출 되지 않게 주의하자


3.Credential과 Provider를 Bean으로 등록

@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가 필요하다.


4. 업로드 서비스 작성

@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()

}
  1. 먼저 파일 이름앞에 UUID를 Prefix로 붙인다.
  2. ObjectName의 "/"는 파일의 path를 의미하는데, YYYY-MM-DD/{domainName}/{fileName}의 형태로 버킷에 저장된다.
  3. S3Presigner는 사용 후 Close를 해줘야 하는데, 코틀린의 use(try-with-resources)를 사용 하였다.
  4. PresignedUrl 생성은 네트워크 통신을 하지 않기 때문에 withContext(Dispathcer)가 필요없다.
  5. 업로드는 Put 권한을 가진 Url을 반환한다.

5.다운로드 서비스 작성

  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을 반환한다.


6. 동작테스트

포스트 맨으로 테스트해보자


위와 같이 요청을 보내면

응답이 오는데 이 응답 주소를 Put 메서드로 아래와 같이 하게 되면

S3에 정상적으로 업로드 되었다.

업로드 된 파일의 객체 URL을 접속하면 아래와 같이 나타나는데, 이는 퍼블릭 액세스를 차단했기 때문이다.

마지막으로, 파일을 다운로드 해보자

이제 위 주소를 Get Method로 호출하면

저장했던 사진이 정상적으로 나오는 것을 확인 할 수 있다.

7. 참고자료

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

profile
왜? 에 대해 생각하려고 노력하는 개발자
post-custom-banner

2개의 댓글

comment-user-thumbnail
2024년 4월 18일

잘 보고 갑니다.

1개의 답글