[Java] S3Presigner added to aws-sdk-java-v2

2 minute read

TL;DR

  • S3Presigner added to AWS SDK for Java 2.0
  • Supports virtual-hosted style by default
  • Supports https by default
  • Change of expiration date setting method
  • Use S3Presigner to generate a signed URL

Introduction

This article is intended for backends that use AWS with Java and Scala, and for people who are engaged in SRE work.

What is an S3 signed URL?

S3 has a mechanism to restrict access to the object by signing the URL issued for downloading and uploading the object.

AWS SDK for Java 2.10.12 or earlier

Prior to AWS SDK for Java 2.10.12, AwsS3V4Signer was used to generate the Presigned URL. The sample code below is written in scala.

before.scala


import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider
import software.amazon.awssdk.auth.signer.AwsS3V4Signer
import software.amazon.awssdk.auth.signer.params.Aws4PresignerParams
import software.amazon.awssdk.http.{SdkHttpFullRequest, SdkHttpMethod}

val request = SdkHttpFullRequest
  .builder()
  .encodedPath("bucket/key")
  .host("s3.ap-northeast-1.amazonaws.com")
  .method(SdkHttpMethod.GET)
  .protocol("https")
  .build()

val params = Aws4PresignerParams
  .builder()
  .expirationTime(Instant.now().plusSeconds(300)
  .awsCredentials(DefaultCredentialsProvider.create().resolveCredentials())
  .signingName("s3")
  .signingRegion("ap-northeast-1")
  .build()

AwsS3V4Signer.create().presign(request, params).getUri

// format of the generated signed URL
// https://s3.ap-northeast-1.amazonaws.com/bucket/key?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20200625T000000Z&X-Amz-SignedHeaders=host&X-Amz-Expires= 300&X-Amz-Credential=XXX&X-Amz-Signature=YYY

AWS SDK for Java 2.10.12 or later

S3Presigner has been added from AWS SDK for Java 2.10.12 1. You can still use AwsS3V4Signer, but it is recommended to use S3Presigner. The reason for this will be described later, but AwsS3V4Signer is still used for the internal implementation of S3Presigner.

after.scala


import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.services.s3.model.GetObjectRequest
import software.amazon.awssdk.services.s3.presigner.S3Presigner
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest

val presigner: S3Presigner = S3Presigner
    .builder()
    .region("ap-northeast-1")
    .build()

val request = GetObjectRequest
  .builder()
  .bucket("bucket")
  .key("key")
  .build()

val presignRequest = GetObjectPresignRequest
  .builder()
  .signatureDuration(Duration.ofSeconds(300))
  .getObjectRequest(request)
  .build()

presigner.presignGetObject(presignRequest).url.toURI

// format of the generated signed URL
// https://bucket.s3.ap-northeast-1.amazonaws.com/key?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20200625T000000Z&X-Amz-SignedHeaders=host&X-Amz-Expires= 300&X-Amz-Credential=XXX&X-Amz-Signature=YYY

What has changed

1. virtual-hosted style

If you compare the generated URLs before and after, you can see that the bucket names are in different positions.

"https://s3.ap-northeast-1.amazonaws.com/bucket/key" // Previous: path style
"https://bucket.s3.ap-northeast-1.amazonaws.com/key" // After: virtual-hosted style

See below for the differences between the two.

  1. Request in path format
  2. Virtual Hosting Format Request
val request = SdkHttpFullRequest
  .builder()
  .encodedPath("key")
  .host("bucket.s3.ap-northeast-1.amazonaws.com")
  .method(SdkHttpMethod.GET)
  .protocol("https")
  .build()

AwsS3V4Signer can generate virtual-hosted style signed URL by setting SdkHttpFullRequest as above, but S3Presigner does not need to be aware of how to implement it.

2. Protocol specification method

With AwsS3V4Signer, you can specify the protocol as it is in the .protocol("https") part. However, S3Presigner does not have a method to specify the protocol. This is due to https being specified internally by default. It is hard to imagine the scene to use, but it is also possible to select http by overwriting the endpoint as follows.

val presigner: S3Presigner = S3Presigner
    .builder()
    .region("ap-northeast-1")
    .endpointOverride(new URI("http://s3.ap-northeast-1.amazonaws.com"))
    .build()

3. How to specify the expiration date

With AwsS3V4Signer, the method was to specify the future date and time that you want to set as the expiration date. However, S3Presigner uses the method of specifying the validity period as Duration.

// Previous
val params = Aws4PresignerParams
  .builder()
  .expirationTime(Instant.now().plusSeconds(300)

// after
val presignRequest = GetObjectPresignRequest
  .builder()
  .signatureDuration(Duration.ofSeconds(300))

Summary

[Amazon S3 path-style deprecated-and beyond]-](https://aws.amazon.com/jp/blogs/news/amazon-s3-path-deprecation-plan-the-rest-of-the-story/)

AWS has announced that it will not support path style for buckets created after September 30, 2020. Therefore, it can be said that changes to S3Presigner should be dealt with early.

  1. For details on how to add, here