From 4dd16e03e9df4a26f992aaaf6c29cca77e84df95 Mon Sep 17 00:00:00 2001 From: Hardik Singh Behl Date: Thu, 2 May 2024 02:54:00 +0530 Subject: [PATCH 01/25] adding stock image --- .../0139-stamped-envelope-1200x628-branded.jpg | Bin 0 -> 121314 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/images/stock/0139-stamped-envelope-1200x628-branded.jpg diff --git a/assets/images/stock/0139-stamped-envelope-1200x628-branded.jpg b/assets/images/stock/0139-stamped-envelope-1200x628-branded.jpg new file mode 100644 index 0000000000000000000000000000000000000000..625d67601cd29861a3f96592b45e3d304246b4a9 GIT binary patch literal 121314 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

DZO2yPMZZJU*;&BW#x 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 A4gdfE literal 0 HcmV?d00001 From b89368d7b62bb52d3c3e077dfc57b1363b222dda Mon Sep 17 00:00:00 2001 From: Hardik Singh Behl Date: Thu, 2 May 2024 12:54:31 +0530 Subject: [PATCH 02/25] initial incomplete draft --- .../2024/2024-05-03-aws-s3-presigned-urls.md | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 content/blog/2024/2024-05-03-aws-s3-presigned-urls.md diff --git a/content/blog/2024/2024-05-03-aws-s3-presigned-urls.md b/content/blog/2024/2024-05-03-aws-s3-presigned-urls.md new file mode 100644 index 000000000..070901542 --- /dev/null +++ b/content/blog/2024/2024-05-03-aws-s3-presigned-urls.md @@ -0,0 +1,136 @@ +--- +title: "Using AWS S3 Presigned URLs in Spring Boot" +categories: [ "AWS", "Spring Boot", "Java" ] +date: 2024-05-02 00:00:00 +0530 +modified: 2024-05-02 00:00:00 +0530 +authors: [ "hardik" ] +description: "In this article, we demonstrate how to use AWS S3 Presigned URLs in a Spring Boot application to offload file transfers, reducing server load and improving performance. We cover required dependencies, configuration, IAM policy, generation of Presigned URLs and integration testing with LocalStack and Testcontainers." +image: images/stock/0139-stamped-envelope-1200x628-branded.jpg +url: "aws-s3-presigned-urls-spring-boot" +--- + +When building web applications that involve file uploads or downloads, a common approach is to have the files pass through the application server. However, this can lead to **increased load on the server, consuming valuable computing resources, and potentially impacting performance**. A more efficient solution is to **offload file transfers to the client (web browsers, desktop/mobile applications) using Presigned URLs**. + +Presigned URLs are time-limited URLs that **allow clients temporary access to upload or download objects directly to or from the storage solution being used**. These URLs are generated with a specified expiration time, after which they are no longer accessible. + +The storage solution we will be using in this article is Amazon S3 (Simple Storage Service), provided by AWS. However, it's worth noting that **the concept of Presigned URLs is not limited to AWS**. It can also be implemented with other cloud storage services like Google Cloud Storage, DigitalOcean Spaces, etc. + +In this article, we will explore how to generate S3 Presigned URLs in a Spring Boot application using Spring Cloud AWS. We'll go through the necessary configurations and create a service class that exposes methods to generate Presigned URLs for uploading objects to and downloading objects from the specified S3 bucket. + +We will also test our developed Presigned URL functionality using LocalStack and Testcontainers. + +## Use Cases and Benefits of Presigned URLs + +Before diving into the implementation, let's further discuss the use cases and advantages of using S3 Presigned URLs to offload file transfers from your application server: + +* **Large File Downloads**: When having an entertainment or an e-learning platform that serves video courses to users, instead of serving the large video files directly from our application server, we can generate Presigned URLs for each video file and offload the responsibility of downloading/streaming the video directly from S3 to the client. + + Before generating the Presigned URL, our server can validate/authenticate the user requesting the video content. In addition to this, we can also restrict access to the video content to a specific IP address from which the request originated. + + By implementing Presigned URLs on applications that serve high volume of content from S3, we **reduce the load on our server(s), improve performance and make our architecture scalable**. + +* **Uploading User-Generated Content**: complete this point + +Now that we understand the use cases for which we can implement Presigned URls and it's benefits, let's proceed with the implementation. + +{{% github "https://github.com/thombergs/code-examples/tree/master/aws/spring-cloud-aws-s3" %}} + +## Configurations + +We will be using Spring Cloud AWS to connect to and interact with our provisioned S3 bucket. While interacting with S3 directly through the AWS SDK for Java is possible, it often leads to verbose configuration classes and boilerplate code. + +Spring Cloud AWS simplifies this integration by providing a layer of abstraction over the official SDK, making it easier to interact with services like S3. + +The main dependency that we will need is `spring-cloud-aws-starter-s3`, which contains all S3 related classes needed by our application. + +We will also make use of Spring Cloud AWS BOM (Bill of Materials) to manage the version of S3 starter in our project. The BOM ensures version compatibility between the declared dependencies, avoids conflicts and makes it easier to update versions in the future. + +Here is how our `pom.xml` file would look like: + +```xml + + 3.1.1 + + + + + + io.awspring.cloud + spring-cloud-aws-starter-s3 + + + + + + + io.awspring.cloud + spring-cloud-aws + ${spring.cloud.version} + pom + import + + + +``` + +Now, the only thing left in order to allow Spring Cloud AWS to establish connection with the AWS S3 service, is to define the necessary configuration properties in our `application.yaml` file: + +```yaml +spring: + cloud: + aws: + credentials: + access-key: ${AWS_ACCESS_KEY} + secret-key: ${AWS_SECRET_KEY} + s3: + region: ${AWS_S3_REGION} +``` +Spring Cloud AWS will automatically create the necessary configuration beans using the above defined properties, allowing us to interact with the S3 service in our application. + +### S3 Bucket Name and Presigned URL Validity + +write what two properties are needed + +```java +@Getter +@Setter +@Validated +@ConfigurationProperties(prefix = "io.reflectoring.aws.s3") +public class AwsS3BucketProperties { + + @NotBlank(message = "S3 bucket name must be configured") + private String bucketName; + + @Valid + private PresignedUrl presignedUrl = new PresignedUrl(); + + @Getter + @Setter + @Validated + public class PresignedUrl { + + @NotNull(message = "S3 presigned URL validity must be specified") + @Positive(message = "S3 presigned URL validity must be a positive value") + private Integer validity; + + } + +} +``` +explain validations in place + +Below is a snippet of our `application.yaml` file where we have defined the required properties which will be automatically mapped to our above defined class: + +```yaml +io: + reflectoring: + aws: + s3: + bucket-name: ${AWS_S3_BUCKET_NAME} + presigned-url: + validity: ${AWS_S3_PRESIGNED_URL_VALIDITY} +``` + +This setup allows us to externalize the bucket name and the validity duration of the Presigned URLs attributes and easily access it in our code. + +This configuration, assumes that the application will be operating against a single S3 bucket and the defined validity will be applicable for both PUT and GET Presigned URls. If that is not the case for your application, then the `AwsS3BucketProperties` can be modified as per requirement. \ No newline at end of file From 22f6765589d3db42789a7cd1d48663dd5461e085 Mon Sep 17 00:00:00 2001 From: Hardik Singh Behl Date: Fri, 3 May 2024 15:45:18 +0530 Subject: [PATCH 03/25] fix article url --- .../2024/2024-05-03-aws-s3-presigned-urls.md | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/content/blog/2024/2024-05-03-aws-s3-presigned-urls.md b/content/blog/2024/2024-05-03-aws-s3-presigned-urls.md index 070901542..036323edc 100644 --- a/content/blog/2024/2024-05-03-aws-s3-presigned-urls.md +++ b/content/blog/2024/2024-05-03-aws-s3-presigned-urls.md @@ -6,7 +6,7 @@ modified: 2024-05-02 00:00:00 +0530 authors: [ "hardik" ] description: "In this article, we demonstrate how to use AWS S3 Presigned URLs in a Spring Boot application to offload file transfers, reducing server load and improving performance. We cover required dependencies, configuration, IAM policy, generation of Presigned URLs and integration testing with LocalStack and Testcontainers." image: images/stock/0139-stamped-envelope-1200x628-branded.jpg -url: "aws-s3-presigned-urls-spring-boot" +url: "aws-s3-presigned-url-spring-boot" --- When building web applications that involve file uploads or downloads, a common approach is to have the files pass through the application server. However, this can lead to **increased load on the server, consuming valuable computing resources, and potentially impacting performance**. A more efficient solution is to **offload file transfers to the client (web browsers, desktop/mobile applications) using Presigned URLs**. @@ -133,4 +133,38 @@ io: This setup allows us to externalize the bucket name and the validity duration of the Presigned URLs attributes and easily access it in our code. -This configuration, assumes that the application will be operating against a single S3 bucket and the defined validity will be applicable for both PUT and GET Presigned URls. If that is not the case for your application, then the `AwsS3BucketProperties` can be modified as per requirement. \ No newline at end of file +This configuration, assumes that the application will be operating against a single S3 bucket and the defined validity will be applicable for both PUT and GET Presigned URls. If that is not the case for your application, then the `AwsS3BucketProperties` can be modified as per requirement. + +## Generating Presigned URLs + +```java +@Service +@RequiredArgsConstructor +@EnableConfigurationProperties(AwsS3BucketProperties.class) +public class StorageService { + + private final S3Template s3Template; + private final AwsS3BucketProperties awsS3BucketProperties; + + public URL generateViewablePresignedUrl(String objectKey) { + var bucketName = awsS3BucketProperties.getBucketName(); + var urlValidity = awsS3BucketProperties.getPresignedUrl().getValidity(); + var urlValidityDuration = Duration.ofSeconds(urlValidity); + + return s3Template.createSignedGetURL(bucketName, objectKey, urlValidityDuration); + } + + public URL generateUploadablePresignedUrl(String objectKey) { + var bucketName = awsS3BucketProperties.getBucketName(); + var urlValidity = awsS3BucketProperties.getPresignedUrl().getValidity(); + var urlValidityDuration = Duration.ofSeconds(urlValidity); + + return s3Template.createSignedPutURL(bucketName, objectKey, urlValidityDuration); + } + +} +``` + +## Required IAM Permissions + +## Conclusion \ No newline at end of file From 7b48cd35cc3fe2190f976e7fce20adc6abeb650b Mon Sep 17 00:00:00 2001 From: Hardik Singh Behl Date: Thu, 9 May 2024 21:07:36 +0530 Subject: [PATCH 04/25] updating blog date --- ...-presigned-urls.md => 2024-05-12-aws-s3-presigned-urls.md} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename content/blog/2024/{2024-05-03-aws-s3-presigned-urls.md => 2024-05-12-aws-s3-presigned-urls.md} (99%) diff --git a/content/blog/2024/2024-05-03-aws-s3-presigned-urls.md b/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md similarity index 99% rename from content/blog/2024/2024-05-03-aws-s3-presigned-urls.md rename to content/blog/2024/2024-05-12-aws-s3-presigned-urls.md index 036323edc..011dd000b 100644 --- a/content/blog/2024/2024-05-03-aws-s3-presigned-urls.md +++ b/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md @@ -1,8 +1,8 @@ --- title: "Using AWS S3 Presigned URLs in Spring Boot" categories: [ "AWS", "Spring Boot", "Java" ] -date: 2024-05-02 00:00:00 +0530 -modified: 2024-05-02 00:00:00 +0530 +date: 2024-05-12 00:00:00 +0530 +modified: 2024-05-12 00:00:00 +0530 authors: [ "hardik" ] description: "In this article, we demonstrate how to use AWS S3 Presigned URLs in a Spring Boot application to offload file transfers, reducing server load and improving performance. We cover required dependencies, configuration, IAM policy, generation of Presigned URLs and integration testing with LocalStack and Testcontainers." image: images/stock/0139-stamped-envelope-1200x628-branded.jpg From 545a30c18473731c2447e4472d7eed95e9647e6b Mon Sep 17 00:00:00 2001 From: Hardik Singh Behl Date: Thu, 9 May 2024 21:43:40 +0530 Subject: [PATCH 05/25] fix: description --- content/blog/2024/2024-05-12-aws-s3-presigned-urls.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md b/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md index 011dd000b..6520eb515 100644 --- a/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md +++ b/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md @@ -4,7 +4,7 @@ categories: [ "AWS", "Spring Boot", "Java" ] date: 2024-05-12 00:00:00 +0530 modified: 2024-05-12 00:00:00 +0530 authors: [ "hardik" ] -description: "In this article, we demonstrate how to use AWS S3 Presigned URLs in a Spring Boot application to offload file transfers, reducing server load and improving performance. We cover required dependencies, configuration, IAM policy, generation of Presigned URLs and integration testing with LocalStack and Testcontainers." +description: "In this article, we demonstrate how to use AWS S3 Presigned URLs in a Spring Boot application to offload file transfers, reduce server load and improve performance. We cover required dependencies, configuration, IAM policy, generation of Presigned URLs and integration testing with LocalStack and Testcontainers." image: images/stock/0139-stamped-envelope-1200x628-branded.jpg url: "aws-s3-presigned-url-spring-boot" --- From 288f508b090d643e3f851a2e1e86a09b2608c3e9 Mon Sep 17 00:00:00 2001 From: Hardik Singh Behl Date: Fri, 10 May 2024 18:28:07 +0530 Subject: [PATCH 06/25] finalizing initial complete draft --- .../2024/2024-05-12-aws-s3-presigned-urls.md | 269 +++++++++++++++++- 1 file changed, 254 insertions(+), 15 deletions(-) diff --git a/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md b/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md index 6520eb515..c0af9d1c3 100644 --- a/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md +++ b/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md @@ -15,13 +15,13 @@ Presigned URLs are time-limited URLs that **allow clients temporary access to up The storage solution we will be using in this article is Amazon S3 (Simple Storage Service), provided by AWS. However, it's worth noting that **the concept of Presigned URLs is not limited to AWS**. It can also be implemented with other cloud storage services like Google Cloud Storage, DigitalOcean Spaces, etc. -In this article, we will explore how to generate S3 Presigned URLs in a Spring Boot application using Spring Cloud AWS. We'll go through the necessary configurations and create a service class that exposes methods to generate Presigned URLs for uploading objects to and downloading objects from the specified S3 bucket. +In this article, we will explore how to generate Presigned URLs in a Spring Boot application using **Spring Cloud AWS**. We will walk through the required configurations and develop a service class that provides methods for generating Presigned URLs. These URLs will allow the client applications to securely upload and download objects to/from a provisioned S3 bucket. -We will also test our developed Presigned URL functionality using LocalStack and Testcontainers. +We will also test our developed Presigned URL functionality using **LocalStack and Testcontainers**. ## Use Cases and Benefits of Presigned URLs -Before diving into the implementation, let's further discuss the use cases and advantages of using S3 Presigned URLs to offload file transfers from your application server: +Before diving into the implementation, let's further discuss the use cases and advantages of using S3 Presigned URLs to offload file transfers from our application servers: * **Large File Downloads**: When having an entertainment or an e-learning platform that serves video courses to users, instead of serving the large video files directly from our application server, we can generate Presigned URLs for each video file and offload the responsibility of downloading/streaming the video directly from S3 to the client. @@ -29,9 +29,11 @@ Before diving into the implementation, let's further discuss the use cases and a By implementing Presigned URLs on applications that serve high volume of content from S3, we **reduce the load on our server(s), improve performance and make our architecture scalable**. -* **Uploading User-Generated Content**: complete this point +* **Uploading User Generated Content**: In scenarios where our application requires the users to upload files such as profile pictures, KYC documents, or other media content, instead of having the files pass through our application server, we can generate a Presigned URL with the necessary permissions and provide it to the client. -Now that we understand the use cases for which we can implement Presigned URls and it's benefits, let's proceed with the implementation. + This approach not only reduces the load on our application server but also simplifies the upload process. The client can initiate the file upload directly to S3, eliminating the need for temporary storage on our server and the additional step of forwarding the file to S3. + +Now that we understand the use cases for which we can implement Presigned URLs and their benefits, let's proceed with the implementation. {{% github "https://github.com/thombergs/code-examples/tree/master/aws/spring-cloud-aws-s3" %}} @@ -89,7 +91,7 @@ Spring Cloud AWS will automatically create the necessary configuration beans usi ### S3 Bucket Name and Presigned URL Validity -write what two properties are needed +To perform operations against a provisioned S3 bucket, we need to provide it’s name. And to generate Presigned URLs, we need to provide a validity duration. We will store these properties in our project's `application.yaml` file and make use of `@ConfigurationProperties` to map the values to a POJO: ```java @Getter @@ -115,9 +117,17 @@ public class AwsS3BucketProperties { } + public Duration getPresignedUrlValidity() { + var urlValidity = this.presignedUrl.validity; + return Duration.ofSeconds(urlValidity); + } + } ``` -explain validations in place + +We have added validation annotations to ensure that both the bucket name and Presigned URL validity are configured correctly. If any of the validations fail, it will result in the Spring Application Context failing to start up. + +When generating Presigned URLs, the validity duration needs to be provided as an instance of the `Duration` class. To facilitate this, we have also added a `getPresignedUrlValidity()` method in our class. Below is a snippet of our `application.yaml` file where we have defined the required properties which will be automatically mapped to our above defined class: @@ -133,10 +143,12 @@ io: This setup allows us to externalize the bucket name and the validity duration of the Presigned URLs attributes and easily access it in our code. -This configuration, assumes that the application will be operating against a single S3 bucket and the defined validity will be applicable for both PUT and GET Presigned URls. If that is not the case for your application, then the `AwsS3BucketProperties` can be modified as per requirement. +This configuration **assumes that the application will be operating against a single S3 bucket and the defined validity will be applicable for both PUT and GET Presigned URls**. If that is not the case for your application, then the `AwsS3BucketProperties` can be modified as per requirement. ## Generating Presigned URLs +Now that we have our configurations set up, we'll proceed to develop our service class that generates Presigned URLs for uploading and downloading objects: + ```java @Service @RequiredArgsConstructor @@ -148,23 +160,250 @@ public class StorageService { public URL generateViewablePresignedUrl(String objectKey) { var bucketName = awsS3BucketProperties.getBucketName(); - var urlValidity = awsS3BucketProperties.getPresignedUrl().getValidity(); - var urlValidityDuration = Duration.ofSeconds(urlValidity); + var urlValidity = awsS3BucketProperties.getPresignedUrlValidity(); - return s3Template.createSignedGetURL(bucketName, objectKey, urlValidityDuration); + return s3Template.createSignedGetURL(bucketName, objectKey, urlValidity); } public URL generateUploadablePresignedUrl(String objectKey) { var bucketName = awsS3BucketProperties.getBucketName(); - var urlValidity = awsS3BucketProperties.getPresignedUrl().getValidity(); - var urlValidityDuration = Duration.ofSeconds(urlValidity); + var urlValidity = awsS3BucketProperties.getPresignedUrlValidity(); - return s3Template.createSignedPutURL(bucketName, objectKey, urlValidityDuration); + return s3Template.createSignedPutURL(bucketName, objectKey, urlValidity); } } ``` +We have used the `S3Template` class provided by Spring Cloud AWS in our service layer which offers a high level abstraction over the `S3Presigner` class from the official AWS SDK. + +While it is possible to use the `S3Presigner` class directly, `S3Template` reduces boilerplate code and simplifies the generation of Presigned URLs by offering convenient, Spring-friendly methods. + +We also make use of our custom `AwsS3BucketProperties` class to reference the S3 bucket name and the Presigned URL validity duration defined in our `application.yaml` file. ## Required IAM Permissions -## Conclusion \ No newline at end of file +To have our service layer generate Presigned URLs correctly, the IAM user whose security credentials we have configured must have the necessary permissions of `s3:GetObject` and `s3:PutObject`. + +Here is what our policy should look like: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:PutObject" + ], + "Resource": "arn:aws:s3:::bucket-name/*" + } + ] +} +``` + +The above IAM policy conforms to the least privilege principle, by granting only the necessary permissions required for our service layer to generate Presigned URLs. We also specify the bucket ARN in the `Resource` field, further limiting the scope of the IAM policy to work with a single bucket that is provisioned for our application. + +## Integration Testing with LocalStack and Testcontainers + +We cannot conclude this article without testing the code we have written so far. We need to ensure that our configurations and service layer work correctly. We will be making use of LocalStack and Testcontainers, but first let’s look at what these two tools are: + +* LocalStack : is a **cloud service emulator** that enables local development and testing of AWS services, without the need for connecting to a remote cloud provider. We'll be provisioning the required S3 bucket inside this emulator. +* Testcontainers : is a library that **provides lightweight, throwaway instances of Docker containers** for integration testing. We will be starting a LocalStack container via this library. + +The prerequisite for running the LocalStack emulator via Testcontainers is, as you’ve guessed it, **an up-and-running Docker instance**. We need to ensure this prerequisite is met when running the test suite either locally or when using a CI/CD pipeline. + +Let’s start by declaring the required test dependencies in our `pom.xml`: + +```xml + + + org.springframework.boot + spring-boot-starter-test + test + + + org.testcontainers + localstack + test + +``` + +The declared `spring-boot-starter-test` gives us the basic testing toolbox as it transitively includes JUnit, AssertJ and other utility libraries, that we will be needing for writing assertions and running our tests. + +And `org.testcontainers:localstack` dependency will allow us to run the LocalStack emulator inside a disposable Docker container, ensuring an isolated environment for our integration test. + +### Provisioning S3 Bucket using Init Hooks + +In order to upload and download objects from an S3 bucket via Presigned URLs, we need.. an S3 bucket. (big brain stuff 🧠) + +Localstack gives us the ability to create required AWS resources when the container is started via Initialization Hooks. We will be creating a bash script `init-s3-bucket.sh` for this purpose inside our `src/test/resources` folder: + +```bash +#!/bin/bash +bucket_name="reflectoring-bucket" + +awslocal s3api create-bucket --bucket $bucket_name + +echo "S3 bucket '$bucket_name' created successfully" +echo "Executed init-s3-bucket.sh" +``` + +The script creates an S3 bucket with name `reflectoring-bucket`. We will copy this script to the path `/etc/localstack/init/ready.d` inside the LocalStack container for execution in our integration test class. + +### Starting LocalStack via Testcontainers + +At the time of this writing, the latest version of the LocalStack image is `3.4`, we will be using this version in our integration test class: + +```java +@SpringBootTest +class StorageServiceIT { + + private static final LocalStackContainer localStackContainer; + + // Bucket name as configured in src/test/resources/init-s3-bucket.sh + private static final String BUCKET_NAME = "reflectoring-bucket"; + private static final Integer PRESIGNED_URL_VALIDITY = randomValiditySeconds(); + + static { + localStackContainer = new LocalStackContainer(DockerImageName.parse("localstack/localstack:3.4")) + .withCopyFileToContainer(MountableFile.forClasspathResource("init-s3-bucket.sh", 0744), "/etc/localstack/init/ready.d/init-s3-bucket.sh") + .withServices(Service.S3) + .waitingFor(Wait.forLogMessage(".*Executed init-s3-bucket.sh.*", 1)); + localStackContainer.start(); + } + + @DynamicPropertySource + static void properties(DynamicPropertyRegistry registry) { + // spring cloud aws properties + registry.add("spring.cloud.aws.credentials.access-key", localStackContainer::getAccessKey); + registry.add("spring.cloud.aws.credentials.secret-key", localStackContainer::getSecretKey); + registry.add("spring.cloud.aws.s3.region", localStackContainer::getRegion); + registry.add("spring.cloud.aws.s3.endpoint", localStackContainer::getEndpoint); + + // custom properties + registry.add("io.reflectoring.aws.s3.bucket-name", () -> BUCKET_NAME); + registry.add("io.reflectoring.aws.s3.presigned-url.validity", () -> PRESIGNED_URL_VALIDITY); + } + + private static int randomValiditySeconds() { + return ThreadLocalRandom.current().nextInt(5, 11); + } + +} +``` +In our integration test class `StorageServiceIT`, we do the following: + +* Start a new instance of the LocalStack container and enable the **`S3`** service. +* Copy our bash script **`init-s3-bucket.sh`** into the container to ensure bucket creation. +* Configure a strategy to wait for the log **`"Executed init-s3-bucket.sh"`** to be printed, as defined in our init script. +* Configure a small random Presigned URL validity through **`randomValiditySeconds()`**. +* Dynamically define the AWS configuration properties needed by our applications in order to create the required S3 related beans using **`@DynamicPropertySource`**. + +Our `@DynamicPropertySource` code block declares an additional `spring.cloud.aws.s3.endpoint` property, which is not present in the main `application.yaml` file. + +This property is necessary when connecting to the LocalStack container's S3 bucket, `reflectoring-bucket`, as it requires a specific endpoint URL. However, when connecting to an actual AWS S3 bucket, specifying an endpoint URL is not required. AWS automatically uses the default endpoint for each service in the configured region. + +The LocalStack container will be automatically destroyed post test suite execution, hence we do not need to worry about manual cleanups. + +With this setup, our applications will use the started LocalStack container for all interactions with AWS cloud during the execution of our integration test, providing an **isolated and ephemeral testing environment**. + +### Testing the Service Layer + +With the LocalStack container set up successfully via Testcontainers, we can now write test cases to ensure our service layer generates legitimate Presigned URLs that can be used to upload and download objects to/from the provisioned S3 bucket: + +```java +@SpringBootTest +class StorageServiceIT { + + @Autowired + private S3Template s3Template; + + @Autowired + private StorageService storageService; + + // LocalStack setup as seen above + + @Test + void shouldGeneratePresignedUrlToFetchStoredObjectFromBucket() { + // Prepare test file and upload to S3 Bucket + var key = RandomString.make(10) + ".txt"; + var fileContent = RandomString.make(50); + var fileToUpload = createTextFile(key, fileContent); + storageService.save(fileToUpload); + + // Invoke method under test + var presignedUrl = storageService.generateViewablePresignedUrl(key); + + // Perform a GET request to the presigned URL + var restClient = RestClient.builder().build(); + var responseBody = restClient.method(HttpMethod.GET) + .uri(URI.create(presignedUrl.toExternalForm())) + .retrieve() + .body(byte[].class); + + // verify the retrieved content matches the expected file content. + var retrievedContent = new String(responseBody, StandardCharsets.UTF_8); + assertThat(fileContent).isEqualTo(retrievedContent); + } + + private MultipartFile createTextFile(String fileName, String content) { + var fileContentBytes = content.getBytes(); + var inputStream = new ByteArrayInputStream(fileContentBytes); + return new MockMultipartFile(fileName, fileName, "text/plain", inputStream); + } + +} +``` + +In our initial test case, we verify that the `StorageService` class can successfully generate a Presigned URL that can be used to download/view an object from the provisioned S3 bucket. + +We begin by preparing a file with random content and name and save it to our S3 bucket. Then we invoke the `generateViewablePresignedUrl` method exposed by our service layer with the corresponding random file key. + +Finally, we perform an HTTP GET request on the generated Presigned URL and assert that the API response matches with the saved file's content. + +Now, to validate the functionality of uploading an object through the generated Presigned URL: + +```java +@Test +void shouldGeneratePresignedUrlForUploadingObjectToBucket() { + // Prepare test file to upload + var key = RandomString.make(10) + ".txt"; + var fileContent = RandomString.make(50); + var fileToUpload = createTextFile(key, fileContent); + + // Invoke method under test + var presignedUrl = storageService.generateUploadablePresignedUrl(key); + + // Upload the test file using the presigned URL + var restClient = RestClient.builder().build(); + var response = restClient.method(HttpMethod.PUT) + .uri(URI.create(presignedUrl.toExternalForm())) + .body(fileToUpload.getBytes()) + .retrieve() + .toBodilessEntity(); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + + // Verify that the file is saved successfully in S3 bucket + var isFileSaved = s3Template.objectExists(BUCKET_NAME, key); + assertThat(isFileSaved).isTrue(); +} +``` + +In the above test case, we again create a test file with random key and content. We invoke the `generateUploadablePresignedUrl` method of our service layer with the corresponding random file key to generate the Presigned URL. + +We perform an HTTP POST request on the generated Presigned URL and send the contents of the test file in the request body. + +Finally, we make use of `S3Template` to assert that the file is indeed saved in our S3 bucket successfully. + +By executing the above integration test cases, we successfully validate that our service layer generates valid Presigned URLs for both uploading and downloading objects to/from the provisioned S3 bucket. + +## Conclusion + +In this article, we explored how to **generate S3 Presigned URLs in a Spring Boot application using Spring Cloud AWS to offload file transfers from the application server to the client.** + +We discussed the benefits and use cases of using Presigned URLs, such as handling large file downloads and user generated content uploads. We walked through the necessary configurations and developed a service class that generates Presigned URLs for uploading and downloading objects to/from an S3 bucket. + +We also covered the required IAM permissions and tested our implementation using LocalStack and Testcontainers to ensure the functionality works as expected. + +The source code demonstrated throughout this article is available on Github. I would highly encourage you to explore the codebase and set it up locally. \ No newline at end of file From 176b2a011e202d841e7e0aab8250abd2780e0da1 Mon Sep 17 00:00:00 2001 From: Hardik Singh Behl Date: Fri, 10 May 2024 18:30:52 +0530 Subject: [PATCH 07/25] fix: publish date to enable rendering --- content/blog/2024/2024-05-12-aws-s3-presigned-urls.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md b/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md index c0af9d1c3..395704ef3 100644 --- a/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md +++ b/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md @@ -1,8 +1,8 @@ --- title: "Using AWS S3 Presigned URLs in Spring Boot" categories: [ "AWS", "Spring Boot", "Java" ] -date: 2024-05-12 00:00:00 +0530 -modified: 2024-05-12 00:00:00 +0530 +date: 2024-05-10 00:00:00 +0530 +modified: 2024-05-10 00:00:00 +0530 authors: [ "hardik" ] description: "In this article, we demonstrate how to use AWS S3 Presigned URLs in a Spring Boot application to offload file transfers, reduce server load and improve performance. We cover required dependencies, configuration, IAM policy, generation of Presigned URLs and integration testing with LocalStack and Testcontainers." image: images/stock/0139-stamped-envelope-1200x628-branded.jpg From 894cfcd1d8b9a76caaf0d695314dc3d22a79b699 Mon Sep 17 00:00:00 2001 From: Hardik Singh Behl Date: Sat, 11 May 2024 12:06:22 +0530 Subject: [PATCH 08/25] fix: http method --- content/blog/2024/2024-05-12-aws-s3-presigned-urls.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md b/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md index 395704ef3..954a2b13e 100644 --- a/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md +++ b/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md @@ -392,7 +392,7 @@ void shouldGeneratePresignedUrlForUploadingObjectToBucket() { In the above test case, we again create a test file with random key and content. We invoke the `generateUploadablePresignedUrl` method of our service layer with the corresponding random file key to generate the Presigned URL. -We perform an HTTP POST request on the generated Presigned URL and send the contents of the test file in the request body. +We perform an HTTP PUT request on the generated Presigned URL and send the contents of the test file in the request body. Finally, we make use of `S3Template` to assert that the file is indeed saved in our S3 bucket successfully. From 1e5ccbad09da850dc858b9f334f49f62b3831314 Mon Sep 17 00:00:00 2001 From: Hardik Singh Behl Date: Sat, 11 May 2024 12:14:26 +0530 Subject: [PATCH 09/25] improving grammar --- content/blog/2024/2024-05-12-aws-s3-presigned-urls.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md b/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md index 954a2b13e..be57e0bc5 100644 --- a/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md +++ b/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md @@ -1,8 +1,8 @@ --- title: "Using AWS S3 Presigned URLs in Spring Boot" categories: [ "AWS", "Spring Boot", "Java" ] -date: 2024-05-10 00:00:00 +0530 -modified: 2024-05-10 00:00:00 +0530 +date: 2024-05-11 00:00:00 +0530 +modified: 2024-05-11 00:00:00 +0530 authors: [ "hardik" ] description: "In this article, we demonstrate how to use AWS S3 Presigned URLs in a Spring Boot application to offload file transfers, reduce server load and improve performance. We cover required dependencies, configuration, IAM policy, generation of Presigned URLs and integration testing with LocalStack and Testcontainers." image: images/stock/0139-stamped-envelope-1200x628-branded.jpg @@ -206,7 +206,7 @@ The above IAM policy conforms to the least privilege principle, by granting only ## Integration Testing with LocalStack and Testcontainers -We cannot conclude this article without testing the code we have written so far. We need to ensure that our configurations and service layer work correctly. We will be making use of LocalStack and Testcontainers, but first let’s look at what these two tools are: +Before concluding this article, we need to ensure that our configurations and service layer work correctly and are able to generate legitimate Presigned URLs. We will be making use of LocalStack and Testcontainers to do this, but first let's look at what these two tools are: * LocalStack : is a **cloud service emulator** that enables local development and testing of AWS services, without the need for connecting to a remote cloud provider. We'll be provisioning the required S3 bucket inside this emulator. * Testcontainers : is a library that **provides lightweight, throwaway instances of Docker containers** for integration testing. We will be starting a LocalStack container via this library. @@ -356,7 +356,7 @@ class StorageServiceIT { } ``` -In our initial test case, we verify that the `StorageService` class can successfully generate a Presigned URL that can be used to download/view an object from the provisioned S3 bucket. +In our initial test case, we verify that our `StorageService` class can successfully generate a Presigned URL that can be used to download an object from the provisioned S3 bucket. We begin by preparing a file with random content and name and save it to our S3 bucket. Then we invoke the `generateViewablePresignedUrl` method exposed by our service layer with the corresponding random file key. From 7db62ec86dff85849a26d219437c25e65a2d0f77 Mon Sep 17 00:00:00 2001 From: Hardik Singh Behl Date: Sat, 11 May 2024 12:17:58 +0530 Subject: [PATCH 10/25] minor fix --- content/blog/2024/2024-05-12-aws-s3-presigned-urls.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md b/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md index be57e0bc5..d9345717d 100644 --- a/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md +++ b/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md @@ -306,7 +306,7 @@ This property is necessary when connecting to the LocalStack container's S3 buck The LocalStack container will be automatically destroyed post test suite execution, hence we do not need to worry about manual cleanups. -With this setup, our applications will use the started LocalStack container for all interactions with AWS cloud during the execution of our integration test, providing an **isolated and ephemeral testing environment**. +With this setup, our application will use the started LocalStack container for all interactions with AWS cloud during the execution of our integration test, providing an **isolated and ephemeral testing environment**. ### Testing the Service Layer From 9c39246338cd2765ec27cf5f4c545af66dde2cd2 Mon Sep 17 00:00:00 2001 From: Hardik Singh Behl Date: Sat, 11 May 2024 12:27:19 +0530 Subject: [PATCH 11/25] grammatical fix --- content/blog/2024/2024-05-12-aws-s3-presigned-urls.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md b/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md index d9345717d..0e0e9a280 100644 --- a/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md +++ b/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md @@ -91,7 +91,7 @@ Spring Cloud AWS will automatically create the necessary configuration beans usi ### S3 Bucket Name and Presigned URL Validity -To perform operations against a provisioned S3 bucket, we need to provide it’s name. And to generate Presigned URLs, we need to provide a validity duration. We will store these properties in our project's `application.yaml` file and make use of `@ConfigurationProperties` to map the values to a POJO: +To perform operations against a provisioned S3 bucket, we need to provide its name. And to generate Presigned URLs, we need to provide a validity duration. We will store these properties in our project's `application.yaml` file and make use of `@ConfigurationProperties` to map the values to a POJO: ```java @Getter From 8bef1dc5305d718f1458f3ef5cf9df6fe3629f84 Mon Sep 17 00:00:00 2001 From: Hardik Singh Behl Date: Sat, 25 May 2024 17:04:23 +0530 Subject: [PATCH 12/25] updating article date --- ...-presigned-urls.md => 2024-05-25-aws-s3-presigned-urls.md} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename content/blog/2024/{2024-05-12-aws-s3-presigned-urls.md => 2024-05-25-aws-s3-presigned-urls.md} (99%) diff --git a/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md b/content/blog/2024/2024-05-25-aws-s3-presigned-urls.md similarity index 99% rename from content/blog/2024/2024-05-12-aws-s3-presigned-urls.md rename to content/blog/2024/2024-05-25-aws-s3-presigned-urls.md index 0e0e9a280..49bbefc2e 100644 --- a/content/blog/2024/2024-05-12-aws-s3-presigned-urls.md +++ b/content/blog/2024/2024-05-25-aws-s3-presigned-urls.md @@ -1,8 +1,8 @@ --- title: "Using AWS S3 Presigned URLs in Spring Boot" categories: [ "AWS", "Spring Boot", "Java" ] -date: 2024-05-11 00:00:00 +0530 -modified: 2024-05-11 00:00:00 +0530 +date: 2024-05-25 00:00:00 +0530 +modified: 2024-05-25 00:00:00 +0530 authors: [ "hardik" ] description: "In this article, we demonstrate how to use AWS S3 Presigned URLs in a Spring Boot application to offload file transfers, reduce server load and improve performance. We cover required dependencies, configuration, IAM policy, generation of Presigned URLs and integration testing with LocalStack and Testcontainers." image: images/stock/0139-stamped-envelope-1200x628-branded.jpg From 7e0d8d78226fc63e16bfdfab4285bd4374d91175 Mon Sep 17 00:00:00 2001 From: Hardik Singh Behl Date: Tue, 28 May 2024 10:14:58 +0530 Subject: [PATCH 13/25] updating title case --- ...resigned-urls.md => 2024-05-28-aws-s3-presigned-urls.md} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename content/blog/2024/{2024-05-25-aws-s3-presigned-urls.md => 2024-05-28-aws-s3-presigned-urls.md} (99%) diff --git a/content/blog/2024/2024-05-25-aws-s3-presigned-urls.md b/content/blog/2024/2024-05-28-aws-s3-presigned-urls.md similarity index 99% rename from content/blog/2024/2024-05-25-aws-s3-presigned-urls.md rename to content/blog/2024/2024-05-28-aws-s3-presigned-urls.md index 49bbefc2e..edd3cfc1b 100644 --- a/content/blog/2024/2024-05-25-aws-s3-presigned-urls.md +++ b/content/blog/2024/2024-05-28-aws-s3-presigned-urls.md @@ -1,8 +1,8 @@ --- title: "Using AWS S3 Presigned URLs in Spring Boot" categories: [ "AWS", "Spring Boot", "Java" ] -date: 2024-05-25 00:00:00 +0530 -modified: 2024-05-25 00:00:00 +0530 +date: 2024-05-28 00:00:00 +0530 +modified: 2024-05-28 00:00:00 +0530 authors: [ "hardik" ] description: "In this article, we demonstrate how to use AWS S3 Presigned URLs in a Spring Boot application to offload file transfers, reduce server load and improve performance. We cover required dependencies, configuration, IAM policy, generation of Presigned URLs and integration testing with LocalStack and Testcontainers." image: images/stock/0139-stamped-envelope-1200x628-branded.jpg @@ -233,7 +233,7 @@ The declared `spring-boot-starter-test` gives us the basic testing toolbox as it And `org.testcontainers:localstack` dependency will allow us to run the LocalStack emulator inside a disposable Docker container, ensuring an isolated environment for our integration test. -### Provisioning S3 Bucket using Init Hooks +### Provisioning S3 Bucket Using Init Hooks In order to upload and download objects from an S3 bucket via Presigned URLs, we need.. an S3 bucket. (big brain stuff 🧠) From 3d9e910d41c70d5a1a5850355fa0630a53131389 Mon Sep 17 00:00:00 2001 From: Hardik Singh Behl Date: Tue, 28 May 2024 10:28:39 +0530 Subject: [PATCH 14/25] updating order of sections --- content/blog/2024/2024-05-28-aws-s3-presigned-urls.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/blog/2024/2024-05-28-aws-s3-presigned-urls.md b/content/blog/2024/2024-05-28-aws-s3-presigned-urls.md index edd3cfc1b..8215a9578 100644 --- a/content/blog/2024/2024-05-28-aws-s3-presigned-urls.md +++ b/content/blog/2024/2024-05-28-aws-s3-presigned-urls.md @@ -9,9 +9,9 @@ image: images/stock/0139-stamped-envelope-1200x628-branded.jpg url: "aws-s3-presigned-url-spring-boot" --- -When building web applications that involve file uploads or downloads, a common approach is to have the files pass through the application server. However, this can lead to **increased load on the server, consuming valuable computing resources, and potentially impacting performance**. A more efficient solution is to **offload file transfers to the client (web browsers, desktop/mobile applications) using Presigned URLs**. +When building web applications that involve file uploads or downloads, a common approach is to have the files pass through an application server. However, this can lead to **increased load on the server, consuming valuable computing resources, and potentially impacting performance**. A more efficient solution is to **offload file transfers to the client (web browsers, desktop/mobile applications) using Presigned URLs**. -Presigned URLs are time-limited URLs that **allow clients temporary access to upload or download objects directly to or from the storage solution being used**. These URLs are generated with a specified expiration time, after which they are no longer accessible. +Presigned URLs are **time-limited URLs that allow clients temporary access to upload or download objects directly to or from the storage solution being used**. These URLs are generated with a specified expiration time, after which they are no longer accessible. The storage solution we will be using in this article is Amazon S3 (Simple Storage Service), provided by AWS. However, it's worth noting that **the concept of Presigned URLs is not limited to AWS**. It can also be implemented with other cloud storage services like Google Cloud Storage, DigitalOcean Spaces, etc. @@ -19,6 +19,8 @@ In this article, we will explore how to generate Presigned URLs in a Spring Boot We will also test our developed Presigned URL functionality using **LocalStack and Testcontainers**. +{{% github "https://github.com/thombergs/code-examples/tree/master/aws/spring-cloud-aws-s3" %}} + ## Use Cases and Benefits of Presigned URLs Before diving into the implementation, let's further discuss the use cases and advantages of using S3 Presigned URLs to offload file transfers from our application servers: @@ -35,8 +37,6 @@ Before diving into the implementation, let's further discuss the use cases and a Now that we understand the use cases for which we can implement Presigned URLs and their benefits, let's proceed with the implementation. -{{% github "https://github.com/thombergs/code-examples/tree/master/aws/spring-cloud-aws-s3" %}} - ## Configurations We will be using Spring Cloud AWS to connect to and interact with our provisioned S3 bucket. While interacting with S3 directly through the AWS SDK for Java is possible, it often leads to verbose configuration classes and boilerplate code. From 9a3e46d1cb523107cba181e008136bcb53d93f94 Mon Sep 17 00:00:00 2001 From: Hardik Singh Behl Date: Tue, 28 May 2024 11:48:06 +0530 Subject: [PATCH 15/25] minor improvements --- .../2024/2024-05-28-aws-s3-presigned-urls.md | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/content/blog/2024/2024-05-28-aws-s3-presigned-urls.md b/content/blog/2024/2024-05-28-aws-s3-presigned-urls.md index 8215a9578..769286a35 100644 --- a/content/blog/2024/2024-05-28-aws-s3-presigned-urls.md +++ b/content/blog/2024/2024-05-28-aws-s3-presigned-urls.md @@ -13,21 +13,21 @@ When building web applications that involve file uploads or downloads, a common Presigned URLs are **time-limited URLs that allow clients temporary access to upload or download objects directly to or from the storage solution being used**. These URLs are generated with a specified expiration time, after which they are no longer accessible. -The storage solution we will be using in this article is Amazon S3 (Simple Storage Service), provided by AWS. However, it's worth noting that **the concept of Presigned URLs is not limited to AWS**. It can also be implemented with other cloud storage services like Google Cloud Storage, DigitalOcean Spaces, etc. +The storage solution we'll be using in this article is Amazon S3 (Simple Storage Service), provided by AWS. However, it's worth noting that **the concept of Presigned URLs is not limited to AWS**. It can also be implemented with other cloud storage services like Google Cloud Storage, DigitalOcean Spaces, etc. -In this article, we will explore how to generate Presigned URLs in a Spring Boot application using **Spring Cloud AWS**. We will walk through the required configurations and develop a service class that provides methods for generating Presigned URLs. These URLs will allow the client applications to securely upload and download objects to/from a provisioned S3 bucket. +In this article, we'll discuss how to generate Presigned URLs in a Spring Boot application to delegate the responsibility of uploading/downloading files to the client. We'll be using **Spring Cloud AWS** to communicate with Amazon S3 and develop a service class that provides methods for generating Presigned URLs. -We will also test our developed Presigned URL functionality using **LocalStack and Testcontainers**. +These URLs will allow the client applications to securely upload and download objects to/from a provisioned S3 bucket. We'll also test our developed Presigned URL functionality using **LocalStack and Testcontainers**. {{% github "https://github.com/thombergs/code-examples/tree/master/aws/spring-cloud-aws-s3" %}} ## Use Cases and Benefits of Presigned URLs -Before diving into the implementation, let's further discuss the use cases and advantages of using S3 Presigned URLs to offload file transfers from our application servers: +Before diving into the implementation, let's further discuss the use cases and advantages of using Presigned URLs to offload file transfers from our application servers: -* **Large File Downloads**: When having an entertainment or an e-learning platform that serves video courses to users, instead of serving the large video files directly from our application server, we can generate Presigned URLs for each video file and offload the responsibility of downloading/streaming the video directly from S3 to the client. +* **Large File Downloads**: When having an entertainment or an e-learning platform that serves video courses to users, instead of serving the large video files from our application server, we can generate Presigned URLs for each video file and offload the responsibility of downloading/streaming the video directly from S3 to the client. - Before generating the Presigned URL, our server can validate/authenticate the user requesting the video content. In addition to this, we can also restrict access to the video content to a specific IP address from which the request originated. + To secure this architecture, before we generate the Presigned URLs, our server can validate/authenticate the user requesting the video content. Additionally, we can restrict access to the video content to the specific IP address that originated the request. By implementing Presigned URLs on applications that serve high volume of content from S3, we **reduce the load on our server(s), improve performance and make our architecture scalable**. @@ -39,13 +39,13 @@ Now that we understand the use cases for which we can implement Presigned URLs a ## Configurations -We will be using Spring Cloud AWS to connect to and interact with our provisioned S3 bucket. While interacting with S3 directly through the AWS SDK for Java is possible, it often leads to verbose configuration classes and boilerplate code. +We'll be using Spring Cloud AWS to connect to and interact with our provisioned S3 bucket. While interacting with S3 directly through the AWS SDK for Java is possible, it often leads to verbose configuration classes and boilerplate code. Spring Cloud AWS simplifies this integration by providing a layer of abstraction over the official SDK, making it easier to interact with services like S3. -The main dependency that we will need is `spring-cloud-aws-starter-s3`, which contains all S3 related classes needed by our application. +The main dependency that we need is `spring-cloud-aws-starter-s3`, which contains all S3 related classes needed by our application. -We will also make use of Spring Cloud AWS BOM (Bill of Materials) to manage the version of S3 starter in our project. The BOM ensures version compatibility between the declared dependencies, avoids conflicts and makes it easier to update versions in the future. +We will also make use of Spring Cloud AWS BOM (Bill of Materials) to manage the version of the S3 starter in our project. The BOM ensures version compatibility between the declared dependencies, avoids conflicts and makes it easier to update versions in the future. Here is how our `pom.xml` file would look like: @@ -75,7 +75,7 @@ Here is how our `pom.xml` file would look like: ``` -Now, the only thing left in order to allow Spring Cloud AWS to establish connection with the AWS S3 service, is to define the necessary configuration properties in our `application.yaml` file: +Now, the only thing left in order to allow Spring Cloud AWS to establish a connection with the Amazon S3 service, is to define the necessary configuration properties in our `application.yaml` file: ```yaml spring: @@ -91,7 +91,7 @@ Spring Cloud AWS will automatically create the necessary configuration beans usi ### S3 Bucket Name and Presigned URL Validity -To perform operations against a provisioned S3 bucket, we need to provide its name. And to generate Presigned URLs, we need to provide a validity duration. We will store these properties in our project's `application.yaml` file and make use of `@ConfigurationProperties` to map the values to a POJO: +To perform operations against a provisioned S3 bucket, we need to provide its name. And to generate Presigned URLs, we need to provide a validity duration. We'll store these properties in our `application.yaml` file and make use of `@ConfigurationProperties` to map the values to a POJO: ```java @Getter @@ -125,11 +125,11 @@ public class AwsS3BucketProperties { } ``` -We have added validation annotations to ensure that both the bucket name and Presigned URL validity are configured correctly. If any of the validations fail, it will result in the Spring Application Context failing to start up. +We've added validation annotations to ensure that both the bucket name and Presigned URL validity are configured correctly. If any of the validations fail, it will result in the Spring Application Context failing to start up. This allows us to conform to the fail fast principle. -When generating Presigned URLs, the validity duration needs to be provided as an instance of the `Duration` class. To facilitate this, we have also added a `getPresignedUrlValidity()` method in our class. +When generating Presigned URLs, the validity duration needs to be provided as an instance of the `Duration` class. To facilitate this, we've also added a `getPresignedUrlValidity()` method in our class that'll be invoked by our service layer. -Below is a snippet of our `application.yaml` file where we have defined the required properties which will be automatically mapped to our above defined class: +Below is a snippet of our `application.yaml` file, which defines the required properties that will be automatically mapped to our `AwsS3BucketProperties` class defined above: ```yaml io: @@ -143,7 +143,7 @@ io: This setup allows us to externalize the bucket name and the validity duration of the Presigned URLs attributes and easily access it in our code. -This configuration **assumes that the application will be operating against a single S3 bucket and the defined validity will be applicable for both PUT and GET Presigned URls**. If that is not the case for your application, then the `AwsS3BucketProperties` can be modified as per requirement. +This configuration **assumes that the application will be operating against a single S3 bucket and the defined validity will be applicable for both PUT and GET Presigned URLs**. If that is not the case for your application, then the `AwsS3BucketProperties` class can be modified as per requirement. ## Generating Presigned URLs @@ -176,7 +176,7 @@ public class StorageService { ``` We have used the `S3Template` class provided by Spring Cloud AWS in our service layer which offers a high level abstraction over the `S3Presigner` class from the official AWS SDK. -While it is possible to use the `S3Presigner` class directly, `S3Template` reduces boilerplate code and simplifies the generation of Presigned URLs by offering convenient, Spring-friendly methods. +While it's possible to use the `S3Presigner` class directly, `S3Template` reduces boilerplate code and simplifies the generation of Presigned URLs by offering convenient, Spring-friendly methods. We also make use of our custom `AwsS3BucketProperties` class to reference the S3 bucket name and the Presigned URL validity duration defined in our `application.yaml` file. @@ -202,14 +202,14 @@ Here is what our policy should look like: } ``` -The above IAM policy conforms to the least privilege principle, by granting only the necessary permissions required for our service layer to generate Presigned URLs. We also specify the bucket ARN in the `Resource` field, further limiting the scope of the IAM policy to work with a single bucket that is provisioned for our application. +The above IAM policy **conforms to the least privilege principle**, by granting only the necessary permissions required for our service layer to generate Presigned URLs. We also specify the bucket ARN in the `Resource` field, further limiting the scope of the IAM policy to work with a single bucket that is provisioned for our application. ## Integration Testing with LocalStack and Testcontainers -Before concluding this article, we need to ensure that our configurations and service layer work correctly and are able to generate legitimate Presigned URLs. We will be making use of LocalStack and Testcontainers to do this, but first let's look at what these two tools are: +Before concluding this article, we need to ensure that our configurations and service layer work correctly and are able to generate legitimate Presigned URLs. We'll be making use of LocalStack and Testcontainers to do this, but first let's look at what these two tools are: * LocalStack : is a **cloud service emulator** that enables local development and testing of AWS services, without the need for connecting to a remote cloud provider. We'll be provisioning the required S3 bucket inside this emulator. -* Testcontainers : is a library that **provides lightweight, throwaway instances of Docker containers** for integration testing. We will be starting a LocalStack container via this library. +* Testcontainers : is a library that **provides lightweight, throwaway instances of Docker containers** for integration testing. We'll be starting a LocalStack container via this library. The prerequisite for running the LocalStack emulator via Testcontainers is, as you’ve guessed it, **an up-and-running Docker instance**. We need to ensure this prerequisite is met when running the test suite either locally or when using a CI/CD pipeline. @@ -235,9 +235,9 @@ And `org.testcontainers:localstack` dependency will allow us to run the LocalSta ### Provisioning S3 Bucket Using Init Hooks -In order to upload and download objects from an S3 bucket via Presigned URLs, we need.. an S3 bucket. (big brain stuff 🧠) +In order to upload and download objects from an S3 bucket via Presigned URLs, we need... **an S3 bucket**. (big brain stuff 🧠) -Localstack gives us the ability to create required AWS resources when the container is started via Initialization Hooks. We will be creating a bash script `init-s3-bucket.sh` for this purpose inside our `src/test/resources` folder: +Localstack gives us the ability to create required AWS resources when the container is started via Initialization Hooks. We'll be creating a bash script `init-s3-bucket.sh` for this purpose inside our `src/test/resources` folder: ```bash #!/bin/bash @@ -249,11 +249,11 @@ echo "S3 bucket '$bucket_name' created successfully" echo "Executed init-s3-bucket.sh" ``` -The script creates an S3 bucket with name `reflectoring-bucket`. We will copy this script to the path `/etc/localstack/init/ready.d` inside the LocalStack container for execution in our integration test class. +The script creates an S3 bucket with name `reflectoring-bucket`. We'll copy this script to the path `/etc/localstack/init/ready.d` inside the LocalStack container for execution in our integration test class. ### Starting LocalStack via Testcontainers -At the time of this writing, the latest version of the LocalStack image is `3.4`, we will be using this version in our integration test class: +At the time of this writing, the latest version of the LocalStack image is `3.4`, we'll be using this version in our integration test class: ```java @SpringBootTest @@ -292,17 +292,17 @@ class StorageServiceIT { } ``` -In our integration test class `StorageServiceIT`, we do the following: +That's a lot of setup code 😥, let's break it down. In our integration test class `StorageServiceIT`, we do the following: * Start a new instance of the LocalStack container and enable the **`S3`** service. * Copy our bash script **`init-s3-bucket.sh`** into the container to ensure bucket creation. * Configure a strategy to wait for the log **`"Executed init-s3-bucket.sh"`** to be printed, as defined in our init script. -* Configure a small random Presigned URL validity through **`randomValiditySeconds()`**. +* Configure a small random Presigned URL validity using **`randomValiditySeconds()`**. * Dynamically define the AWS configuration properties needed by our applications in order to create the required S3 related beans using **`@DynamicPropertySource`**. Our `@DynamicPropertySource` code block declares an additional `spring.cloud.aws.s3.endpoint` property, which is not present in the main `application.yaml` file. -This property is necessary when connecting to the LocalStack container's S3 bucket, `reflectoring-bucket`, as it requires a specific endpoint URL. However, when connecting to an actual AWS S3 bucket, specifying an endpoint URL is not required. AWS automatically uses the default endpoint for each service in the configured region. +**This property is necessary when connecting to the LocalStack container's S3 bucket**, `reflectoring-bucket`, as it requires a specific endpoint URL. However, when connecting to an actual AWS S3 bucket, specifying an endpoint URL is not required. AWS automatically uses the default endpoint for each service in the configured region. The LocalStack container will be automatically destroyed post test suite execution, hence we do not need to worry about manual cleanups. @@ -356,7 +356,7 @@ class StorageServiceIT { } ``` -In our initial test case, we verify that our `StorageService` class can successfully generate a Presigned URL that can be used to download an object from the provisioned S3 bucket. +In our initial test case, we verify that our `StorageService` class successfully generates a Presigned URL that can be used to download an object from the provisioned S3 bucket. We begin by preparing a file with random content and name and save it to our S3 bucket. Then we invoke the `generateViewablePresignedUrl` method exposed by our service layer with the corresponding random file key. @@ -400,7 +400,9 @@ By executing the above integration test cases, we successfully validate that our ## Conclusion -In this article, we explored how to **generate S3 Presigned URLs in a Spring Boot application using Spring Cloud AWS to offload file transfers from the application server to the client.** +In this article, we explored how to **generate Presigned URLs in a Spring Boot application to offload file transfers from the application server to the client.** + +We used **Spring Cloud AWS** to communicate with our Amazon S3 bucket and reduced boilerplate configuration code. We discussed the benefits and use cases of using Presigned URLs, such as handling large file downloads and user generated content uploads. We walked through the necessary configurations and developed a service class that generates Presigned URLs for uploading and downloading objects to/from an S3 bucket. From 57e3a5c3b21d73eee669bb5ff55c0d812988be65 Mon Sep 17 00:00:00 2001 From: Hardik Singh Behl Date: Tue, 28 May 2024 12:06:09 +0530 Subject: [PATCH 16/25] updating title --- content/blog/2024/2024-05-28-aws-s3-presigned-urls.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blog/2024/2024-05-28-aws-s3-presigned-urls.md b/content/blog/2024/2024-05-28-aws-s3-presigned-urls.md index 769286a35..883e985dd 100644 --- a/content/blog/2024/2024-05-28-aws-s3-presigned-urls.md +++ b/content/blog/2024/2024-05-28-aws-s3-presigned-urls.md @@ -1,5 +1,5 @@ --- -title: "Using AWS S3 Presigned URLs in Spring Boot" +title: "Offloading File Transfers with Amazon S3 Presigned URLs in Spring Boot" categories: [ "AWS", "Spring Boot", "Java" ] date: 2024-05-28 00:00:00 +0530 modified: 2024-05-28 00:00:00 +0530 From 9ec2630bc6f17e177da7b7b74711115072db3d5f Mon Sep 17 00:00:00 2001 From: Hardik Singh Behl Date: Tue, 28 May 2024 12:06:44 +0530 Subject: [PATCH 17/25] updating title --- content/blog/2024/2024-05-27-spring-cloud-aws-s3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blog/2024/2024-05-27-spring-cloud-aws-s3.md b/content/blog/2024/2024-05-27-spring-cloud-aws-s3.md index 76e323d06..b6aafaaed 100644 --- a/content/blog/2024/2024-05-27-spring-cloud-aws-s3.md +++ b/content/blog/2024/2024-05-27-spring-cloud-aws-s3.md @@ -1,5 +1,5 @@ --- -title: "Using Amazon S3 with Spring Cloud AWS" +title: "Integrating Amazon S3 with Spring Boot using Spring Cloud AWS" categories: [ "AWS", "Spring Boot", "Java" ] date: 2024-05-27 00:00:00 +0530 modified: 2024-05-27 00:00:00 +0530 From 883e4ff9c6e616611301c3e3fee88ee8009aad7c Mon Sep 17 00:00:00 2001 From: Hardik Singh Behl Date: Tue, 28 May 2024 12:16:51 +0530 Subject: [PATCH 18/25] updating article URLs --- content/blog/2024/2024-05-27-spring-cloud-aws-s3.md | 2 +- content/blog/2024/2024-05-28-aws-s3-presigned-urls.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/content/blog/2024/2024-05-27-spring-cloud-aws-s3.md b/content/blog/2024/2024-05-27-spring-cloud-aws-s3.md index b6aafaaed..b29473b3a 100644 --- a/content/blog/2024/2024-05-27-spring-cloud-aws-s3.md +++ b/content/blog/2024/2024-05-27-spring-cloud-aws-s3.md @@ -6,7 +6,7 @@ modified: 2024-05-27 00:00:00 +0530 authors: [ "hardik" ] description: "In this article, we learn how AWS S3 can be integrated in a Spring Boot application using Spring Cloud AWS. The article details the necessary configurations, best practices, IAM policy and integration testing with LocalStack and Testcontainers." image: images/stock/0138-bucket-alternative-1200x628-branded.jpg -url: "spring-cloud-aws-s3" +url: "integrating-amazon-s3-with-spring-boot-using-spring-cloud-aws" --- In modern web applications, storing and retrieving files has become a common requirement. Whether it is user uploaded content like images and documents or application generated logs and reports, having a reliable and scalable storage solution is crucial. diff --git a/content/blog/2024/2024-05-28-aws-s3-presigned-urls.md b/content/blog/2024/2024-05-28-aws-s3-presigned-urls.md index 883e985dd..21c39762e 100644 --- a/content/blog/2024/2024-05-28-aws-s3-presigned-urls.md +++ b/content/blog/2024/2024-05-28-aws-s3-presigned-urls.md @@ -6,7 +6,7 @@ modified: 2024-05-28 00:00:00 +0530 authors: [ "hardik" ] description: "In this article, we demonstrate how to use AWS S3 Presigned URLs in a Spring Boot application to offload file transfers, reduce server load and improve performance. We cover required dependencies, configuration, IAM policy, generation of Presigned URLs and integration testing with LocalStack and Testcontainers." image: images/stock/0139-stamped-envelope-1200x628-branded.jpg -url: "aws-s3-presigned-url-spring-boot" +url: "offloading-file-transfers-with-amazon-s3-presigned-urls-in-spring-boot" --- When building web applications that involve file uploads or downloads, a common approach is to have the files pass through an application server. However, this can lead to **increased load on the server, consuming valuable computing resources, and potentially impacting performance**. A more efficient solution is to **offload file transfers to the client (web browsers, desktop/mobile applications) using Presigned URLs**. From 15768312c3e680e0323ede56636c7a3d6af1dc98 Mon Sep 17 00:00:00 2001 From: Hardik Singh Behl Date: Tue, 28 May 2024 12:19:35 +0530 Subject: [PATCH 19/25] fix: artile title case --- content/blog/2024/2024-05-27-spring-cloud-aws-s3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blog/2024/2024-05-27-spring-cloud-aws-s3.md b/content/blog/2024/2024-05-27-spring-cloud-aws-s3.md index b29473b3a..f9a82ac48 100644 --- a/content/blog/2024/2024-05-27-spring-cloud-aws-s3.md +++ b/content/blog/2024/2024-05-27-spring-cloud-aws-s3.md @@ -1,5 +1,5 @@ --- -title: "Integrating Amazon S3 with Spring Boot using Spring Cloud AWS" +title: "Integrating Amazon S3 with Spring Boot Using Spring Cloud AWS" categories: [ "AWS", "Spring Boot", "Java" ] date: 2024-05-27 00:00:00 +0530 modified: 2024-05-27 00:00:00 +0530 From 837209f964fe147faa0631ded191647385388b20 Mon Sep 17 00:00:00 2001 From: Hardik Singh Behl Date: Mon, 3 Jun 2024 19:25:45 +0530 Subject: [PATCH 20/25] updating date --- ...-presigned-urls.md => 2024-06-03-aws-s3-presigned-urls.md} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename content/blog/2024/{2024-05-28-aws-s3-presigned-urls.md => 2024-06-03-aws-s3-presigned-urls.md} (99%) diff --git a/content/blog/2024/2024-05-28-aws-s3-presigned-urls.md b/content/blog/2024/2024-06-03-aws-s3-presigned-urls.md similarity index 99% rename from content/blog/2024/2024-05-28-aws-s3-presigned-urls.md rename to content/blog/2024/2024-06-03-aws-s3-presigned-urls.md index 21c39762e..4f201c680 100644 --- a/content/blog/2024/2024-05-28-aws-s3-presigned-urls.md +++ b/content/blog/2024/2024-06-03-aws-s3-presigned-urls.md @@ -1,8 +1,8 @@ --- title: "Offloading File Transfers with Amazon S3 Presigned URLs in Spring Boot" categories: [ "AWS", "Spring Boot", "Java" ] -date: 2024-05-28 00:00:00 +0530 -modified: 2024-05-28 00:00:00 +0530 +date: 2024-06-03 00:00:00 +0530 +modified: 2024-06-03 00:00:00 +0530 authors: [ "hardik" ] description: "In this article, we demonstrate how to use AWS S3 Presigned URLs in a Spring Boot application to offload file transfers, reduce server load and improve performance. We cover required dependencies, configuration, IAM policy, generation of Presigned URLs and integration testing with LocalStack and Testcontainers." image: images/stock/0139-stamped-envelope-1200x628-branded.jpg From 302a0c0bba41357e5c4a3a0ddbbf4cd7361aa15a Mon Sep 17 00:00:00 2001 From: michaelk Date: Wed, 5 Jun 2024 11:31:04 +0200 Subject: [PATCH 21/25] Some minor edits --- ...ls.md => 2024-06-05-aws-s3-presigned-urls.md} | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) rename content/blog/2024/{2024-06-03-aws-s3-presigned-urls.md => 2024-06-05-aws-s3-presigned-urls.md} (94%) diff --git a/content/blog/2024/2024-06-03-aws-s3-presigned-urls.md b/content/blog/2024/2024-06-05-aws-s3-presigned-urls.md similarity index 94% rename from content/blog/2024/2024-06-03-aws-s3-presigned-urls.md rename to content/blog/2024/2024-06-05-aws-s3-presigned-urls.md index 4f201c680..79c00c41c 100644 --- a/content/blog/2024/2024-06-03-aws-s3-presigned-urls.md +++ b/content/blog/2024/2024-06-05-aws-s3-presigned-urls.md @@ -1,8 +1,8 @@ --- title: "Offloading File Transfers with Amazon S3 Presigned URLs in Spring Boot" categories: [ "AWS", "Spring Boot", "Java" ] -date: 2024-06-03 00:00:00 +0530 -modified: 2024-06-03 00:00:00 +0530 +date: 2024-06-05 00:00:00 +0530 +modified: 2024-06-05 00:00:00 +0530 authors: [ "hardik" ] description: "In this article, we demonstrate how to use AWS S3 Presigned URLs in a Spring Boot application to offload file transfers, reduce server load and improve performance. We cover required dependencies, configuration, IAM policy, generation of Presigned URLs and integration testing with LocalStack and Testcontainers." image: images/stock/0139-stamped-envelope-1200x628-branded.jpg @@ -13,7 +13,7 @@ When building web applications that involve file uploads or downloads, a common Presigned URLs are **time-limited URLs that allow clients temporary access to upload or download objects directly to or from the storage solution being used**. These URLs are generated with a specified expiration time, after which they are no longer accessible. -The storage solution we'll be using in this article is Amazon S3 (Simple Storage Service), provided by AWS. However, it's worth noting that **the concept of Presigned URLs is not limited to AWS**. It can also be implemented with other cloud storage services like Google Cloud Storage, DigitalOcean Spaces, etc. +The storage solution we'll use in this article is Amazon S3 (Simple Storage Service), provided by AWS. However, it's worth noting that **the concept of Presigned URLs is not limited to AWS**. It can also be implemented with other cloud storage services like Google Cloud Storage, DigitalOcean Spaces, etc. In this article, we'll discuss how to generate Presigned URLs in a Spring Boot application to delegate the responsibility of uploading/downloading files to the client. We'll be using **Spring Cloud AWS** to communicate with Amazon S3 and develop a service class that provides methods for generating Presigned URLs. @@ -31,7 +31,7 @@ Before diving into the implementation, let's further discuss the use cases and a By implementing Presigned URLs on applications that serve high volume of content from S3, we **reduce the load on our server(s), improve performance and make our architecture scalable**. -* **Uploading User Generated Content**: In scenarios where our application requires the users to upload files such as profile pictures, KYC documents, or other media content, instead of having the files pass through our application server, we can generate a Presigned URL with the necessary permissions and provide it to the client. +* **Uploading User-Generated Content**: In scenarios where our application requires the users to upload files such as profile pictures, documents, or other media content, instead of having the files pass through our application server, we can generate a Presigned URL with the necessary permissions and provide it to the client. This approach not only reduces the load on our application server but also simplifies the upload process. The client can initiate the file upload directly to S3, eliminating the need for temporary storage on our server and the additional step of forwarding the file to S3. @@ -229,7 +229,7 @@ Let’s start by declaring the required test dependencies in our `pom.xml`: ``` -The declared `spring-boot-starter-test` gives us the basic testing toolbox as it transitively includes JUnit, AssertJ and other utility libraries, that we will be needing for writing assertions and running our tests. +The declared `spring-boot-starter-test` gives us the basic testing toolbox as it transitively includes JUnit, AssertJ, and other utility libraries, that we will be needing for writing assertions and running our tests. And `org.testcontainers:localstack` dependency will allow us to run the LocalStack emulator inside a disposable Docker container, ensuring an isolated environment for our integration test. @@ -298,7 +298,7 @@ That's a lot of setup code 😥, let's break it down. In our integration test cl * Copy our bash script **`init-s3-bucket.sh`** into the container to ensure bucket creation. * Configure a strategy to wait for the log **`"Executed init-s3-bucket.sh"`** to be printed, as defined in our init script. * Configure a small random Presigned URL validity using **`randomValiditySeconds()`**. -* Dynamically define the AWS configuration properties needed by our applications in order to create the required S3 related beans using **`@DynamicPropertySource`**. +* Dynamically define the AWS configuration properties needed by our applications to create the required S3-related beans using **`@DynamicPropertySource`**. Our `@DynamicPropertySource` code block declares an additional `spring.cloud.aws.s3.endpoint` property, which is not present in the main `application.yaml` file. @@ -360,7 +360,7 @@ In our initial test case, we verify that our `StorageService` class successfully We begin by preparing a file with random content and name and save it to our S3 bucket. Then we invoke the `generateViewablePresignedUrl` method exposed by our service layer with the corresponding random file key. -Finally, we perform an HTTP GET request on the generated Presigned URL and assert that the API response matches with the saved file's content. +Finally, we perform an HTTP GET request on the generated Presigned URL and assert that the API response matches the saved file's content. Now, to validate the functionality of uploading an object through the generated Presigned URL: @@ -404,7 +404,7 @@ In this article, we explored how to **generate Presigned URLs in a Spring Boot a We used **Spring Cloud AWS** to communicate with our Amazon S3 bucket and reduced boilerplate configuration code. -We discussed the benefits and use cases of using Presigned URLs, such as handling large file downloads and user generated content uploads. We walked through the necessary configurations and developed a service class that generates Presigned URLs for uploading and downloading objects to/from an S3 bucket. +We discussed the benefits and use cases of using Presigned URLs, such as handling large file downloads and user-generated content uploads. We walked through the necessary configurations and developed a service class that generates Presigned URLs for uploading and downloading objects to/from an S3 bucket. We also covered the required IAM permissions and tested our implementation using LocalStack and Testcontainers to ensure the functionality works as expected. From a8e96da3af9b3364847d08e9d4e41f7c8e8167c0 Mon Sep 17 00:00:00 2001 From: Hardik Singh Behl Date: Fri, 7 Jun 2024 11:13:10 +0530 Subject: [PATCH 22/25] adding security details --- .../2024/2024-06-05-aws-s3-presigned-urls.md | 69 +++++++++++++++++-- 1 file changed, 63 insertions(+), 6 deletions(-) diff --git a/content/blog/2024/2024-06-05-aws-s3-presigned-urls.md b/content/blog/2024/2024-06-05-aws-s3-presigned-urls.md index 79c00c41c..600f84535 100644 --- a/content/blog/2024/2024-06-05-aws-s3-presigned-urls.md +++ b/content/blog/2024/2024-06-05-aws-s3-presigned-urls.md @@ -1,15 +1,17 @@ --- title: "Offloading File Transfers with Amazon S3 Presigned URLs in Spring Boot" categories: [ "AWS", "Spring Boot", "Java" ] -date: 2024-06-05 00:00:00 +0530 -modified: 2024-06-05 00:00:00 +0530 +date: 2024-06-07 00:00:00 +0530 +modified: 2024-06-07 00:00:00 +0530 authors: [ "hardik" ] description: "In this article, we demonstrate how to use AWS S3 Presigned URLs in a Spring Boot application to offload file transfers, reduce server load and improve performance. We cover required dependencies, configuration, IAM policy, generation of Presigned URLs and integration testing with LocalStack and Testcontainers." image: images/stock/0139-stamped-envelope-1200x628-branded.jpg url: "offloading-file-transfers-with-amazon-s3-presigned-urls-in-spring-boot" --- -When building web applications that involve file uploads or downloads, a common approach is to have the files pass through an application server. However, this can lead to **increased load on the server, consuming valuable computing resources, and potentially impacting performance**. A more efficient solution is to **offload file transfers to the client (web browsers, desktop/mobile applications) using Presigned URLs**. +When building web applications that involve file uploads or downloads, a common approach is to have the files pass through an application server. However, this can lead to **increased load on the server, consuming valuable computing resources, and potentially impacting performance**. A more efficient solution is to **offload file transfers to the client using Presigned URLs**. + +In the context of this article, the client refers to the single page application (SPA), desktop, or mobile application that the user is interacting with, and not the end user themselves. Presigned URLs are **time-limited URLs that allow clients temporary access to upload or download objects directly to or from the storage solution being used**. These URLs are generated with a specified expiration time, after which they are no longer accessible. @@ -27,7 +29,7 @@ Before diving into the implementation, let's further discuss the use cases and a * **Large File Downloads**: When having an entertainment or an e-learning platform that serves video courses to users, instead of serving the large video files from our application server, we can generate Presigned URLs for each video file and offload the responsibility of downloading/streaming the video directly from S3 to the client. - To secure this architecture, before we generate the Presigned URLs, our server can validate/authenticate the user requesting the video content. Additionally, we can restrict access to the video content to the specific IP address that originated the request. + To secure this architecture, before we generate the Presigned URLs, our server can validate/authenticate the user requesting the video content. Additionally, we can restrict access to the video content to the specific IP address that originated the request via IAM policy. By implementing Presigned URLs on applications that serve high volume of content from S3, we **reduce the load on our server(s), improve performance and make our architecture scalable**. @@ -143,7 +145,9 @@ io: This setup allows us to externalize the bucket name and the validity duration of the Presigned URLs attributes and easily access it in our code. -This configuration **assumes that the application will be operating against a single S3 bucket and the defined validity will be applicable for both PUT and GET Presigned URLs**. If that is not the case for your application, then the `AwsS3BucketProperties` class can be modified as per requirement. +For the sake of demonstration, the defined configuration **assumes that the application will be operating against a single S3 bucket and the defined validity will be applicable for both PUT and GET Presigned URLs**. Should this not align with your application's requirements, then the `AwsS3BucketProperties` class can be modified accordingly. + +I'd recommend keeping the validity of both PUT and GET Presigned URLs separate to enjoy more flexibility, and also would like to emphasize that the **validity duration of the Presigned URLs should be kept as short as possible, especially for upload operations**, to protect against unauthorized access. ## Generating Presigned URLs @@ -180,6 +184,8 @@ While it's possible to use the `S3Presigner` class directly, `S3Template` reduce We also make use of our custom `AwsS3BucketProperties` class to reference the S3 bucket name and the Presigned URL validity duration defined in our `application.yaml` file. +It's important to ensure that the **controller API endpoints that'll consumes the service class we've created, should be secured and preferably rate limited**. Before our application generates a Presigned URL, the requesting user's identity and authority should be validated to prevent unauthorized access. + ## Required IAM Permissions To have our service layer generate Presigned URLs correctly, the IAM user whose security credentials we have configured must have the necessary permissions of `s3:GetObject` and `s3:PutObject`. @@ -204,6 +210,55 @@ Here is what our policy should look like: The above IAM policy **conforms to the least privilege principle**, by granting only the necessary permissions required for our service layer to generate Presigned URLs. We also specify the bucket ARN in the `Resource` field, further limiting the scope of the IAM policy to work with a single bucket that is provisioned for our application. +Additionally, we can further restrict access to our S3 bucket by allowing requests only from a specific IP address or IP address blocks where our client application is hosted. + +To achieve this, we can modify our IAM policy statement to include a condition that checks the source IP address of the request using CIDR notation: + +```json +"Condition": { + "IpAddress": { + "aws:SourceIp": "01.02.03.04/32" + } +} +``` + +## Enabling Cross-Origin Resource Sharing (CORS) + +There's one last thing we need to configure before our Presigned URLs can be invoked from our client applications. + +When invoking Presigned URLs from a client such as a single page application (SPA), we'll encounter an error in our browser console: + +```console +Access to XMLHttpRequest at 'https://..presigned-url' from origin 'http://ourdomain.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. +``` + +Ah, the infamous CORS error! 😩 + +We encounter this error because web browsers implement the **Same-Origin Policy** mechanism by default, which restricts our client application to interact with a resource from another origin (S3 bucket in this context). + +In order to solve this, we'll need to add a CORS configuration to our provisioned S3 bucket: + +```json +[ + { + "AllowedMethods": [ + "GET", + "PUT" + ], + "AllowedOrigins": [ + "http://localhost:8081" + ], + "AllowedHeaders": [], + "ExposeHeaders": [], + "MaxAgeSeconds": 3000 + } +] +``` + +The above configuration allows our client application, hosted at `http://ourdomain.com`, to send HTTP GET and PUT requests to access our provisioned S3 bucket. + +In our configuration, we follow the least privilege principle similar to our IAM policy and grant access only to the necessary origin and methods required by our application. **An overly permissive CORS configuration that uses a wildcard `*` for all properties should be avoided**, as it can introduce security vulnerabilities. + ## Integration Testing with LocalStack and Testcontainers Before concluding this article, we need to ensure that our configurations and service layer work correctly and are able to generate legitimate Presigned URLs. We'll be making use of LocalStack and Testcontainers to do this, but first let's look at what these two tools are: @@ -406,6 +461,8 @@ We used **Spring Cloud AWS** to communicate with our Amazon S3 bucket and reduce We discussed the benefits and use cases of using Presigned URLs, such as handling large file downloads and user-generated content uploads. We walked through the necessary configurations and developed a service class that generates Presigned URLs for uploading and downloading objects to/from an S3 bucket. -We also covered the required IAM permissions and tested our implementation using LocalStack and Testcontainers to ensure the functionality works as expected. +Throughout the article, we've discussed various security considerations and best practices to strengthen our architecture's security and mitigate risks, such as keeping the validity of the Presigned URL short, securing the controller API endpoints that generate Presigned URLs, and following the least privilege principle when defining the IAM policy and CORS configuration. + +We also tested our implementation using LocalStack and Testcontainers to ensure the developed functionality works as expected. The source code demonstrated throughout this article is available on Github. I would highly encourage you to explore the codebase and set it up locally. \ No newline at end of file From 19a30ce210a79475c9527b976d9c6fe8cc671c6e Mon Sep 17 00:00:00 2001 From: Hardik Singh Behl Date: Fri, 7 Jun 2024 17:52:43 +0530 Subject: [PATCH 23/25] covering mobile application IP based restriction issue --- .../blog/2024/2024-06-05-aws-s3-presigned-urls.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/content/blog/2024/2024-06-05-aws-s3-presigned-urls.md b/content/blog/2024/2024-06-05-aws-s3-presigned-urls.md index 600f84535..9124572ed 100644 --- a/content/blog/2024/2024-06-05-aws-s3-presigned-urls.md +++ b/content/blog/2024/2024-06-05-aws-s3-presigned-urls.md @@ -9,9 +9,7 @@ image: images/stock/0139-stamped-envelope-1200x628-branded.jpg url: "offloading-file-transfers-with-amazon-s3-presigned-urls-in-spring-boot" --- -When building web applications that involve file uploads or downloads, a common approach is to have the files pass through an application server. However, this can lead to **increased load on the server, consuming valuable computing resources, and potentially impacting performance**. A more efficient solution is to **offload file transfers to the client using Presigned URLs**. - -In the context of this article, the client refers to the single page application (SPA), desktop, or mobile application that the user is interacting with, and not the end user themselves. +When building web applications that involve file uploads or downloads, a common approach is to have the files pass through an application server. However, this can lead to **increased load on the server, consuming valuable computing resources, and potentially impacting performance**. A more efficient solution is to **offload file transfers to the client (web browsers, desktop/mobile applications) using Presigned URLs**. Presigned URLs are **time-limited URLs that allow clients temporary access to upload or download objects directly to or from the storage solution being used**. These URLs are generated with a specified expiration time, after which they are no longer accessible. @@ -217,10 +215,19 @@ To achieve this, we can modify our IAM policy statement to include a condition t ```json "Condition": { "IpAddress": { - "aws:SourceIp": "01.02.03.04/32" + "aws:SourceIp": [ + "01.02.03.04/32" + ] } } ``` +While the above condition will work for a Single Page Application (SPA), hosted on a server with a static IP address, it'll not be a suitable solution if our client is a mobile application. + +A mobile device often switches between different networks and can be assigned different IP addresses by its carrier or Wi-Fi network. This makes it difficult to apply an IP-based restriction in our IAM policy. + +While it's possible to update the IAM policy configuration per session by our Spring Boot application, it's not recommended at all. 🚫 + +Updating IAM policies programmatically for each client session would be an administrative overhead, and would require granting our backend application permission to modify IAM policies, which goes against the principle of least privilege. **It is generally recommended to keep IAM permissions separate from application-level permissions**. ## Enabling Cross-Origin Resource Sharing (CORS) From 5d8fc243857b2775236829a9ae7d28aa3b303c38 Mon Sep 17 00:00:00 2001 From: Hardik Singh Behl Date: Wed, 12 Jun 2024 03:53:46 +0530 Subject: [PATCH 24/25] removing IP address restriction details --- .../2024/2024-06-05-aws-s3-presigned-urls.md | 27 +++---------------- 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/content/blog/2024/2024-06-05-aws-s3-presigned-urls.md b/content/blog/2024/2024-06-05-aws-s3-presigned-urls.md index 9124572ed..c50c5e82a 100644 --- a/content/blog/2024/2024-06-05-aws-s3-presigned-urls.md +++ b/content/blog/2024/2024-06-05-aws-s3-presigned-urls.md @@ -1,8 +1,8 @@ --- title: "Offloading File Transfers with Amazon S3 Presigned URLs in Spring Boot" categories: [ "AWS", "Spring Boot", "Java" ] -date: 2024-06-07 00:00:00 +0530 -modified: 2024-06-07 00:00:00 +0530 +date: 2024-06-12 00:00:00 +0530 +modified: 2024-06-12 00:00:00 +0530 authors: [ "hardik" ] description: "In this article, we demonstrate how to use AWS S3 Presigned URLs in a Spring Boot application to offload file transfers, reduce server load and improve performance. We cover required dependencies, configuration, IAM policy, generation of Presigned URLs and integration testing with LocalStack and Testcontainers." image: images/stock/0139-stamped-envelope-1200x628-branded.jpg @@ -27,7 +27,7 @@ Before diving into the implementation, let's further discuss the use cases and a * **Large File Downloads**: When having an entertainment or an e-learning platform that serves video courses to users, instead of serving the large video files from our application server, we can generate Presigned URLs for each video file and offload the responsibility of downloading/streaming the video directly from S3 to the client. - To secure this architecture, before we generate the Presigned URLs, our server can validate/authenticate the user requesting the video content. Additionally, we can restrict access to the video content to the specific IP address that originated the request via IAM policy. + To secure this architecture, before we generate the Presigned URLs, our server can validate/authenticate the user requesting the video content. By implementing Presigned URLs on applications that serve high volume of content from S3, we **reduce the load on our server(s), improve performance and make our architecture scalable**. @@ -208,27 +208,6 @@ Here is what our policy should look like: The above IAM policy **conforms to the least privilege principle**, by granting only the necessary permissions required for our service layer to generate Presigned URLs. We also specify the bucket ARN in the `Resource` field, further limiting the scope of the IAM policy to work with a single bucket that is provisioned for our application. -Additionally, we can further restrict access to our S3 bucket by allowing requests only from a specific IP address or IP address blocks where our client application is hosted. - -To achieve this, we can modify our IAM policy statement to include a condition that checks the source IP address of the request using CIDR notation: - -```json -"Condition": { - "IpAddress": { - "aws:SourceIp": [ - "01.02.03.04/32" - ] - } -} -``` -While the above condition will work for a Single Page Application (SPA), hosted on a server with a static IP address, it'll not be a suitable solution if our client is a mobile application. - -A mobile device often switches between different networks and can be assigned different IP addresses by its carrier or Wi-Fi network. This makes it difficult to apply an IP-based restriction in our IAM policy. - -While it's possible to update the IAM policy configuration per session by our Spring Boot application, it's not recommended at all. 🚫 - -Updating IAM policies programmatically for each client session would be an administrative overhead, and would require granting our backend application permission to modify IAM policies, which goes against the principle of least privilege. **It is generally recommended to keep IAM permissions separate from application-level permissions**. - ## Enabling Cross-Origin Resource Sharing (CORS) There's one last thing we need to configure before our Presigned URLs can be invoked from our client applications. From 04364a0834a6dd63d06ba8808d4b3db934b95864 Mon Sep 17 00:00:00 2001 From: michaelk Date: Sat, 15 Jun 2024 13:54:55 +0200 Subject: [PATCH 25/25] Final edit and prepare for publishing --- ...md => 2024-06-15-aws-s3-presigned-urls.md} | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) rename content/blog/2024/{2024-06-05-aws-s3-presigned-urls.md => 2024-06-15-aws-s3-presigned-urls.md} (92%) diff --git a/content/blog/2024/2024-06-05-aws-s3-presigned-urls.md b/content/blog/2024/2024-06-15-aws-s3-presigned-urls.md similarity index 92% rename from content/blog/2024/2024-06-05-aws-s3-presigned-urls.md rename to content/blog/2024/2024-06-15-aws-s3-presigned-urls.md index c50c5e82a..9266b94bd 100644 --- a/content/blog/2024/2024-06-05-aws-s3-presigned-urls.md +++ b/content/blog/2024/2024-06-15-aws-s3-presigned-urls.md @@ -1,8 +1,8 @@ --- title: "Offloading File Transfers with Amazon S3 Presigned URLs in Spring Boot" categories: [ "AWS", "Spring Boot", "Java" ] -date: 2024-06-12 00:00:00 +0530 -modified: 2024-06-12 00:00:00 +0530 +date: 2024-06-15 00:00:00 +0530 +modified: 2024-06-15 00:00:00 +0530 authors: [ "hardik" ] description: "In this article, we demonstrate how to use AWS S3 Presigned URLs in a Spring Boot application to offload file transfers, reduce server load and improve performance. We cover required dependencies, configuration, IAM policy, generation of Presigned URLs and integration testing with LocalStack and Testcontainers." image: images/stock/0139-stamped-envelope-1200x628-branded.jpg @@ -11,7 +11,7 @@ url: "offloading-file-transfers-with-amazon-s3-presigned-urls-in-spring-boot" When building web applications that involve file uploads or downloads, a common approach is to have the files pass through an application server. However, this can lead to **increased load on the server, consuming valuable computing resources, and potentially impacting performance**. A more efficient solution is to **offload file transfers to the client (web browsers, desktop/mobile applications) using Presigned URLs**. -Presigned URLs are **time-limited URLs that allow clients temporary access to upload or download objects directly to or from the storage solution being used**. These URLs are generated with a specified expiration time, after which they are no longer accessible. +Presigned URLs are **time-limited URLs that allow clients temporary access to upload or download objects directly to or from the storage solution**. These URLs are generated with a specified expiration time, after which they are no longer accessible. The storage solution we'll use in this article is Amazon S3 (Simple Storage Service), provided by AWS. However, it's worth noting that **the concept of Presigned URLs is not limited to AWS**. It can also be implemented with other cloud storage services like Google Cloud Storage, DigitalOcean Spaces, etc. @@ -29,7 +29,7 @@ Before diving into the implementation, let's further discuss the use cases and a To secure this architecture, before we generate the Presigned URLs, our server can validate/authenticate the user requesting the video content. - By implementing Presigned URLs on applications that serve high volume of content from S3, we **reduce the load on our server(s), improve performance and make our architecture scalable**. + By implementing Presigned URLs on applications that serve a high volume of content from S3, we **reduce the load on our server(s), improve performance, and make our architecture scalable**. * **Uploading User-Generated Content**: In scenarios where our application requires the users to upload files such as profile pictures, documents, or other media content, instead of having the files pass through our application server, we can generate a Presigned URL with the necessary permissions and provide it to the client. @@ -43,9 +43,9 @@ We'll be using Spring Cloud AWSSpring Cloud AWS BOM (Bill of Materials) to manage the version of the S3 starter in our project. The BOM ensures version compatibility between the declared dependencies, avoids conflicts and makes it easier to update versions in the future. +We will also make use of Spring Cloud AWS BOM (Bill of Materials) to manage the version of the S3 starter in our project. The BOM ensures version compatibility between the declared dependencies, avoids conflicts, and makes it easier to update versions in the future. Here is how our `pom.xml` file would look like: @@ -75,7 +75,7 @@ Here is how our `pom.xml` file would look like: ``` -Now, the only thing left in order to allow Spring Cloud AWS to establish a connection with the Amazon S3 service, is to define the necessary configuration properties in our `application.yaml` file: +Now, the only thing left in order to allow Spring Cloud AWS to establish a connection with the Amazon S3 service is to define the necessary configuration properties in our `application.yaml` file: ```yaml spring: @@ -87,11 +87,11 @@ spring: s3: region: ${AWS_S3_REGION} ``` -Spring Cloud AWS will automatically create the necessary configuration beans using the above defined properties, allowing us to interact with the S3 service in our application. +Spring Cloud AWS will automatically create the necessary configuration beans using the above-defined properties, allowing us to interact with the S3 service in our application. ### S3 Bucket Name and Presigned URL Validity -To perform operations against a provisioned S3 bucket, we need to provide its name. And to generate Presigned URLs, we need to provide a validity duration. We'll store these properties in our `application.yaml` file and make use of `@ConfigurationProperties` to map the values to a POJO: +To perform operations against a provisioned S3 bucket, we need to provide its name. To generate Presigned URLs, we need to provide a validity duration. We'll store these properties in our `application.yaml` file and make use of `@ConfigurationProperties` to map the values to a POJO: ```java @Getter @@ -141,7 +141,7 @@ io: validity: ${AWS_S3_PRESIGNED_URL_VALIDITY} ``` -This setup allows us to externalize the bucket name and the validity duration of the Presigned URLs attributes and easily access it in our code. +This setup allows us to externalize the bucket name and the validity duration of the Presigned URL attributes and easily access it in our code. For the sake of demonstration, the defined configuration **assumes that the application will be operating against a single S3 bucket and the defined validity will be applicable for both PUT and GET Presigned URLs**. Should this not align with your application's requirements, then the `AwsS3BucketProperties` class can be modified accordingly. @@ -176,13 +176,13 @@ public class StorageService { } ``` -We have used the `S3Template` class provided by Spring Cloud AWS in our service layer which offers a high level abstraction over the `S3Presigner` class from the official AWS SDK. +We have used the `S3Template` class provided by Spring Cloud AWS in our service layer which offers a high-level abstraction over the `S3Presigner` class from the official AWS SDK. While it's possible to use the `S3Presigner` class directly, `S3Template` reduces boilerplate code and simplifies the generation of Presigned URLs by offering convenient, Spring-friendly methods. We also make use of our custom `AwsS3BucketProperties` class to reference the S3 bucket name and the Presigned URL validity duration defined in our `application.yaml` file. -It's important to ensure that the **controller API endpoints that'll consumes the service class we've created, should be secured and preferably rate limited**. Before our application generates a Presigned URL, the requesting user's identity and authority should be validated to prevent unauthorized access. +It's important to ensure that the **controller API endpoints that'll consume the service class we've created, should be secured and preferably rate-limited**. Before our application generates a Presigned URL, the requesting user's identity and authority should be validated to prevent unauthorized access. ## Required IAM Permissions @@ -220,7 +220,7 @@ Access to XMLHttpRequest at 'https://..presigned-url' from origin 'http://ourdom Ah, the infamous CORS error! 😩 -We encounter this error because web browsers implement the **Same-Origin Policy** mechanism by default, which restricts our client application to interact with a resource from another origin (S3 bucket in this context). +We encounter this error because web browsers implement the **Same-Origin Policy** mechanism by default, which restricts our client application from interacting with a resource from another origin (S3 bucket in this context). In order to solve this, we'll need to add a CORS configuration to our provisioned S3 bucket: @@ -290,7 +290,7 @@ echo "S3 bucket '$bucket_name' created successfully" echo "Executed init-s3-bucket.sh" ``` -The script creates an S3 bucket with name `reflectoring-bucket`. We'll copy this script to the path `/etc/localstack/init/ready.d` inside the LocalStack container for execution in our integration test class. +The script creates an S3 bucket with name the `reflectoring-bucket`. We'll copy this script to the path `/etc/localstack/init/ready.d` inside the LocalStack container for execution in our integration test class. ### Starting LocalStack via Testcontainers @@ -431,7 +431,7 @@ void shouldGeneratePresignedUrlForUploadingObjectToBucket() { } ``` -In the above test case, we again create a test file with random key and content. We invoke the `generateUploadablePresignedUrl` method of our service layer with the corresponding random file key to generate the Presigned URL. +In the above test case, we again create a test file with a random key and content. We invoke the `generateUploadablePresignedUrl` method of our service layer with the corresponding random file key to generate the Presigned URL. We perform an HTTP PUT request on the generated Presigned URL and send the contents of the test file in the request body.