In this tutorial, we will read user data from AWS RDS with an access token. You can have a look at the following tutorials before moving ahead.
- Build and deploy a Serverless Spring Boot Web Application with AWS Lambda
- AWS Lambda with Spring Boot – A Simple GreetMe Example
- Amazon Cognito User Authentication in Spring Boot REST
- Using AWS Lambda to store user data in Amazon RDS
In our previous tutorials, we learned how we can do User Authentication in Spring Boot Rest using Amazon Cognito. Then we learned that we can use AWS Lambda to store user data in Amazon RDS. In this tutorial, we will implement authorized access to Amazon RDS using what we have learned previously.
Let’s get started with the implementation.
Create an API using API Gateway
First, we will log in to our AWS account and click on API Gateway under the Services tab. Then we will click on Create API. Then we will select API type to be HTTP API. Here we will specify the backend services that we want our API to communicate with. Here we will select Lambda under Integrations then in the Lambda function we will select the Lambda function for which we are creating the API i.e DemoLambda. After that, we will provide our API a name i.e. UserDataAPI. Then we will click on Next.
Here we will configure routes to expose integrations to consumers of our API. We will select Method to be GET then click on Next.
Here we will leave the stage to be default then click on Next. We can add stages that represent environments such as development or production.
Now we will review our configurations and then click on Create.
In Authorization currently, we have no Authorizer attached to our API. We will click on Manage Authorizers and then click on Create.
We will select the Authorizer type to be JWT that uses JSON Web Tokens to control access to our API. Then we will provide the name of our Authorizer i.e. CognitoAuthorizer. We will leave the Identity source to be $request.header.Authorization.
Then we will enter the Issuer URL of our Identity provider. In our case, it will be the following.
https://cognito-idp.{region id}.amazonaws.com/{Pool Id}
We can find the value for region id and Pool Id in Amazon Cognito General Settings. Then in Audience, we will provide the client ID that we can find in App Clients under General Settings. After that, we will click on Create.
Now will attach Authorizer to our API. Here we will select CognitoAuthorizer and then click on Attach Authorizer.
Now we can see our API endpoint in Triggers under Configuration of our Lambda function.
Create Lambda Function using Spring Boot
Following is our handler class in which will create a connection to our database using static block so that we don’t have to request a connection every time lambda function is called. In the handler function, we will read data from our table and then return it. Note that here we are using AWSCognitoIdentityProvider so that the data is returned only to the user to which it belongs. Here we get the username of the user from the token by using the getUser API.
Handler.java
import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.services.cognitoidp.AWSCognitoIdentityProvider; import com.amazonaws.services.cognitoidp.AWSCognitoIdentityProviderClientBuilder; import com.amazonaws.services.cognitoidp.model.GetUserRequest; import com.amazonaws.services.cognitoidp.model.GetUserResult; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import com.aws.lambda.requests.ResponseDto; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Map; public class Handler implements RequestHandler<APIGatewayProxyRequestEvent, Object> { static Connection connection; static { try { String username = System.getenv("dbUsername"); String password = System.getenv("dbPassword"); String URL = System.getenv("dbURL"); connection = DriverManager.getConnection(URL, username, password); } catch (SQLException e) { } } @Override public Object handleRequest(APIGatewayProxyRequestEvent event, Context context) { try { Map<String, String> headers = event.getHeaders(); if (headers != null) { String authorization = headers.get("authorization"); if (authorization != null) { String username = getUsername(authorization); if (username != null) { Statement stmt = connection.createStatement(); String query = "SELECT * FROM userprofile where username=\"" + username + "\""; ResultSet rs = stmt.executeQuery(query); ResponseDto userProfile = new ResponseDto(); if (rs.next()) { userProfile.setFirstName(rs.getString("first_name")); userProfile.setLastName(rs.getString("last_name")); userProfile.setAge(rs.getInt("age")); return userProfile; } } } } return null; } catch (Exception e) { return e.getMessage(); } } private String getUsername(String authorization) { String accessKey = System.getenv("accessKey"); String secretKey = System.getenv("secretKey"); String region = System.getenv("region"); BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey); AWSCognitoIdentityProvider cognitoClient = AWSCognitoIdentityProviderClientBuilder.standard() .withCredentials(new AWSStaticCredentialsProvider(awsCreds)).withRegion(region).build(); GetUserRequest getUserRequest = new GetUserRequest(); getUserRequest.setAccessToken(authorization); GetUserResult user = cognitoClient.getUser(getUserRequest); return user.getUsername(); } }
ResponseDto.java
public class ResponseDto { String firstName; String lastName; int age; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
pom.xml
Following is our pom.xml for our lambda function. Note that here through aws-java-sdk-cognitoidp we have been able to decode our access token from Amazon Cognito using AWSCognitoIdentityProvider.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.1.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <artifactId>springboot-aws-lambda</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-aws-lambda</name> <description>Demo project for lambda RDS and Cognito</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.SR6</spring-cloud.version> <wrapper.version>1.0.17.RELEASE</wrapper.version> </properties> <dependencies> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk-cognitoidp</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-function-adapter-aws</artifactId> </dependency> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-lambda-java-events</artifactId> <version>2.0.2</version> </dependency> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-lambda-java-core</artifactId> <version>1.1.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> <configuration> <skip>true</skip> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot.experimental</groupId> <artifactId>spring-boot-thin-layout</artifactId> <version>${wrapper.version}</version> </dependency> </dependencies> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.4</version> <configuration> <createDependencyReducedPom>false</createDependencyReducedPom> <shadedArtifactAttached>true</shadedArtifactAttached> <shadedClassifierName>aws</shadedClassifierName> </configuration> </plugin> </plugins> <finalName>springboot-db</finalName> </build> </project>
Environment variables
We will create Environment variables in our Lambda function so that they can be accessed from our code when we deploy it.
Testing
Now we will test our Lambda function with the API endpoint. Here we will provide the token for the key Authorization in the header. Note that the token is what we received in our previous tutorial. If data exist in our table for the user of whom we have passed the token, the lambda function will return it.
Here the user exists in Amazon Cognito user pool but there is no data present for the user in our table hence the lambda function returns null.
If the token is invalid i.e user does not exist in the Amazon Cognito user pool then the lambda function will return 401 i.e. Unauthorized.
Conclusion
With this, we have to the end of our tutorial. In this tutorial, we learned that how we can read user data from AWS RDS with an access token. First, we walked through the process of the creation of an API that was integrated with our lambda function so that access to it can be authorized. Then we created our lambda function using Spring Boot and in the end, we tested our implementation using Postman Client.
Stay tuned for some more informative tutorials coming ahead. Feel free to leave any feedback or query in the comments section.
Happy learning!