Skip to content

Instantly share code, notes, and snippets.

@aaronanderson
Last active February 6, 2024 10:52
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aaronanderson/f9e2806cc5e2c18fab4d7e60c589d160 to your computer and use it in GitHub Desktop.
Save aaronanderson/f9e2806cc5e2c18fab4d7e60c589d160 to your computer and use it in GitHub Desktop.
Workaround to create presigned S3 URL using the new aws-sdk-java-v2 library preview
//updated for 2.0.0-preview-11
import java.io.ByteArrayInputStream;
import java.net.URI;
import java.time.Duration;
import java.time.Instant;
import java.util.Optional;
import software.amazon.awssdk.auth.signer.AwsS3V4Signer;
import software.amazon.awssdk.auth.signer.internal.AwsSignerExecutionAttribute;
import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.core.interceptor.Context.BeforeTransmission;
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
import software.amazon.awssdk.http.Abortable;
import software.amazon.awssdk.http.AbortableCallable;
import software.amazon.awssdk.http.AbortableInputStream;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.SdkHttpConfigurationOption;
import software.amazon.awssdk.http.SdkHttpFullRequest;
import software.amazon.awssdk.http.SdkHttpFullResponse;
import software.amazon.awssdk.http.SdkRequestContext;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3ClientBuilder;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
public class S3Util {
public static URI presignS3DownloadLink(String bucketName, String fileName) throws SdkClientException {
try {
S3ClientBuilder s3Builder = S3Client.builder().region(Region.US_WEST_1);
S3PresignExecutionInterceptor presignInterceptor = new S3PresignExecutionInterceptor(Region.US_WEST_1, Duration.ofDays(4));
s3Builder.overrideConfiguration(ClientOverrideConfiguration.builder().addExecutionInterceptor(presignInterceptor).build());
s3Builder.httpClient(new NullSdkHttpClient());
S3Client s3Client = s3Builder.build();
GetObjectRequest s3GetRequest = GetObjectRequest.builder().bucket(bucketName).key(fileName).build();
ResponseInputStream<GetObjectResponse> response = s3Client.getObject(s3GetRequest);
response.close();
return presignInterceptor.getSignedURI();
} catch (Throwable t) {
if (t instanceof SdkClientException) {
throw (SdkClientException) t;
}
throw SdkClientException.builder().cause(t).build();
}
}
public static class NullSdkHttpClient implements SdkHttpClient {
@Override
public void close() {
}
@Override
public <T> Optional<T> getConfigurationValue(SdkHttpConfigurationOption<T> key) {
return Optional.empty();
}
@Override
public AbortableCallable<SdkHttpFullResponse> prepareRequest(SdkHttpFullRequest request, SdkRequestContext requestContext) {
return new AbortableCallable<SdkHttpFullResponse>() {
@Override
public SdkHttpFullResponse call() throws Exception {
return SdkHttpFullResponse.builder().statusCode(200).content(new AbortableInputStream(new ByteArrayInputStream(new byte[0]), new Abortable() {
@Override
public void abort() {
}
})).build();
}
@Override
public void abort() {
}
};
}
}
public static class S3PresignExecutionInterceptor implements ExecutionInterceptor {
final private AwsS3V4Signer signer;
final private String serviceName;
final private Region region;
final private Duration expirationTime;
final private Integer timeOffset;
private URI signedURI;
public S3PresignExecutionInterceptor(Region region, Duration expirationTime) {
this.signer = AwsS3V4Signer.create();
this.serviceName = "s3";
this.region = region;
this.expirationTime = expirationTime;
this.timeOffset = 2;
}
@Override
public void beforeTransmission(BeforeTransmission context, ExecutionAttributes executionAttributes) {
// remove all headers because a Browser that downloads the shared URL will not send the exact values. X-Amz-SignedHeaders should only contain the host header.
SdkHttpFullRequest modifiedSdkRequest = context.httpRequest().toBuilder().clearHeaders().build();
executionAttributes.putAttribute(AwsSignerExecutionAttribute.PRESIGNER_EXPIRATION, Instant.ofEpochSecond(0).plus(expirationTime));
executionAttributes.putAttribute(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME, serviceName);
executionAttributes.putAttribute(AwsSignerExecutionAttribute.SIGNING_REGION, region);
executionAttributes.putAttribute(AwsSignerExecutionAttribute.TIME_OFFSET, timeOffset);
SdkHttpFullRequest signedRequest = signer.presign(modifiedSdkRequest, executionAttributes);// sign(getRequest, new ExecutionAttributes());
signedURI = signedRequest.getUri();
}
public URI getSignedURI() {
return signedURI;
}
}
public static void main(String[] args) throws Exception {
System.out.format("Download URL: %s\n", presignS3DownloadLink("some-bucket", "some-file.txt"));
}
}
@mkbrv
Copy link

mkbrv commented Feb 1, 2019

Thank you for the above example and for your effort.

I am trying to get this signed URL to execute a PUT with a file from a browser. Until now all I receive is : The request signature we calculated does not match the signature you provided. Check your key and signing method. .

I suppose it might be related to the file type not being taken into calculation in the above case, while AWS considers it when it calculates the signature. I will keep trying and come back with a solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment