Category Archives: Programming

Integration Testing in Spring Boot Application

In this post, I will show how we can add integration testing to a Spring Boot application.

Integration tests play a key role in ensuring the quality of the application. With a framework like Spring Boot, it is even easier to integrate such tests. Nevertheless, it is important to test applications with integration tests without deploying them to the application server.

Integration tests can help to test the data access layer of your application. Integration tests also help to test multiple units. For the Spring Boot application, we need to run an application in ApplicationContext to be able to run tests. Integration tests can help in testing exception handling.

Spring Boot Application

For this demo, we will build a simple Spring Boot application with REST APIs. We will be using the H2 In-Memory database for storing the data. Eventually, I will show how to write an integration test. This application reads a JSON file of vulnerabilities from the National Vulnerability Database and stores it in the H2 database. REST APIs allow a user to fetch that data in a more readable format.

Dependencies

First, we want to build integration tests in this application, so we will need to include the dependency spring-boot-starter-test .


dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'junit:junit:4.13.1'
	runtimeOnly 'com.h2database:h2:1.4.200'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

This dependency of spring-boot-starter-test allow us to add testing-related annotations that we will see soon.

REST API

Now as I said previously, we will have a REST API to fetch national vulnerability database data. We will create a REST controller with two APIs one to fetch a list of vulnerabilities and one to fetch a vulnerability by CVE id.


@RestController
@RequestMapping("/v1/beacon23/vulnerabilities")
public class CveController
{

    @Autowired
    private CveService cveService;

    @GetMapping("/list")
    public List getAllCveItems(@RequestParam(required = false, name="fromDate") String fromDate, @RequestParam(required = false, name=
            "toDate") String toDate)
    {
        List cveDTOList = cveService.getCveItems(fromDate, toDate);

        if(cveDTOList == null || cveDTOList.isEmpty())
        {
            return new ArrayList<>();
        }
        else
        {
            return cveDTOList;
        }
    }

    @GetMapping
    public ResponseEntity getCveItemById(@RequestParam("cveId") String cveId)
    {
        CveDTO cveDTO = cveService.getCveItemByCveId(cveId);

        if(cveDTO != null)
        {
            return new ResponseEntity<>(cveDTO, HttpStatus.OK);
        }
        else
        {
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        }
    }

}

So we have

  • /v1/beacon23/vulnerabilities/list – to fetch a list of vulnerabilities
  • /v1/beacon23/vulnerabilities?cveId=value – to fetch vulnerability by CVE id.

Service

Now, most of the business logic and validation happen in Service class. As we saw in our API, we use CVEService to fetch the required data.

    @Autowired
    public CveDataDao cveDataDao;

    public List getCveItems(String from, String to)
    {
        LOGGER.debug("The date range values are from = {} and to = {}", from, to);
        List cveDataList = cveDataDao.findAll();
        List cveDTOList = new ArrayList<>();

        for(CveData cveData : cveDataList)
        {
            List cveList = cveData.getCveItems();
            for(CveItem cveItem: cveList)
            {
                Date fromDate;
                Date toDate;

                if(!isNullOrEmpty(from) && !isNullOrEmpty(to))
                {
                    fromDate = DateUtil.formatDate(from);
                    toDate = DateUtil.formatDate(to);

                    Date publishedDate = DateUtil.formatDate(cveItem.getPublishedDate());

                    if(publishedDate.after(toDate) || publishedDate.before(fromDate))
                    {
                        continue;
                    }
                }
                CveDTO cveDTO = convertCveItemToCveDTO(cveItem);
                cveDTOList.add(cveDTO);
            }
        }
        return cveDTOList;
    }

    private boolean isNullOrEmpty (String str)
    {
        return (str == null || str.isEmpty());
    }

    private String buildDescription (List descriptionDataList)
    {
        if(descriptionDataList == null || descriptionDataList.isEmpty())
        {
            return EMPTY_STRING;
        }
        else
        {
            return descriptionDataList.get(0).getValue();
        }
    }

    private List buildReferenceUrls (List referenceDataList)
    {
        return referenceDataList.stream().map(it -> it.getUrl()).collect(Collectors.toList());
    }

    public CveDTO getCveItemByCveId(String cveId)
    {
        List cveDataList = cveDataDao.findAll();
        CveDTO cveDTO = null;

        for(CveData cveData : cveDataList)
        {
            List cveItems = cveData.getCveItems();

            Optional optionalCveItem =
                    cveItems.stream().filter(ci -> ci.getCve().getCveMetadata().getCveId().equals(cveId)).findAny();
            CveItem cveItem = null;
            if(optionalCveItem.isPresent())
            {
                cveItem = optionalCveItem.get();
            }
            else
            {
                return cveDTO;
            }
            cveDTO = convertCveItemToCveDTO(cveItem);
        }

        return cveDTO;
    }

Usage of @SpringBootTest

Spring Boot provides an annotation @SpringBootTest that we can use in integration tests. With this annotation, the tests can start the application context that can contain all the objects we need for the application to run.

Integration tests provide an almost production-like scenario to test our code. The tests annotated with @SpringBootTest create the application context used in our tests through application class annotated with @SpringBootConfiguration.

These tests start an embedded server, create a web environment, and then run @Test methods to do integration testing. We need to add few attributes to make sure we can start web environment while using @SpringBootTest.

  • Attribute webEnvironment – To create a web environment with a default port or a random port.

We can also pass properties to use for tests using an active profile. Usually, we use these profiles for different environments, but we can also use a special profile for tests only. We create application-dev.yml, application-prod.yml profiles. Similarly, we can create application-test.yml and use the annotation @ActiveProfiles('test') in our tests.

Example of Integration Test

For our REST API, we will create an integration test that will test our controller. We will also use TestRestTemplate to fetch data. This integration test will look like below:


package com.betterjavacode.beacon23.tests;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;

import static org.junit.Assert.assertNotNull;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CveControllerTest
{
    @LocalServerPort
    private int port;

    TestRestTemplate testRestTemplate = new TestRestTemplate();

    HttpHeaders headers = new HttpHeaders();

    @Test
    public void testGetAllCveItems()
    {
        HttpEntity entity = new HttpEntity<>(null, headers);

        ResponseEntity responseEntity = testRestTemplate.exchange(createURLWithPort(
                "/v1/beacon23/vulnerabilities/list"),HttpMethod.GET, entity, String.class);

        assertNotNull(responseEntity);

    }


    private String createURLWithPort(String uri)
    {
        return "http://localhost:" + port + uri;
    }
}

We use @SpringBootTest annotation for our test class and set up the application context by using webEnvironment with a RANDOM_PORT. We also mock the local web server by setting up a mock port with @LocalServerPort.

TestRestTemplate allows us to simulate a client that will call our API. Once we run this test (either through gradle build OR through IntelliJ), we will see the Spring Boot Application Context setup running and the application running at a random port.

One disadvantage of creating integration tests with @SpringBootTest is that it will slow down building your application. In most enterprise environments, you will have this set up through continuous integration and continuous deployment. In such scenarios, it slows down the process of integration and deployment if you have a lot of integration tests.

Conclusion

Finally, you should use integration testing in the Spring Boot application or not, it depends on your application. But despite the drawback, it is always useful to have integration tests that allow testing multiple units at a time. @SpringBootTest is a handy annotation that can be used to set up an application context, allowing us to run tests close to a production environment.

References

  1. integration Testing with Spring Boot – Integration Testing

Controller Advice – Exception Handler in Spring Boot

In this post, I will show how we can use the annotation @ControllerAdvice – Controller Advice – an exception handler in the Spring Boot application. If you want to read how to handle uncaught exceptions in Spring Boot, you can check my old post.

What is @ControllerAdvice ?

Spring 3.2 introduced an annotation @ControllerAdvice. The annotation allows the handling of exceptions across the application. Before this, Spring offered another annotation @ExceptionHandler for exception handling. But, you have to add this annotation in each controller class of your application. It doesn’t help on the application level.

@ControllerAdvice is an annotation-driven interceptor. It intercepts most of those classes that include @RequestMapping.

Comparison with @ExceptionHandler

In most controller classes, you can add @ExceptionHandler annotation to handle exceptions for that class. Nevertheless, in such classes, one can add an extra method to handle exceptions thrown by @RequestMapping methods in the same controller. These exception handling methods can redirect the user to the error page OR build a custom error response.

This will look like below:

@RestController
@RequestMapping("/companies")
public class CompanyController
{
  
  @GetMapping
  public List<Company> getAllCompanies(HttpServletRequest req) throws Exception {
    
  } 
  @ExceptionHandler(Exception.class)
  public ModelAndView handleError(HttpServletRequest req, Exception ex) {
    logger.error("Request: " + req.getRequestURL() + " raised " + ex);

    ModelAndView mav = new ModelAndView();
    mav.addObject("exception", ex);
    mav.addObject("url", req.getRequestURL());
    mav.setViewName("error");
    return mav;
  }

}

Additionally, you can see the method getAllCompanies throw an Exception. The method handleError will handle the exception thrown by getAllCompanies.

Furthermore, if I have another controller like UserController, I will end up writing like below if it has to handle exceptions.

@RestController
@RequestMapping("/users")
public class UserController
{
  
  @GetMapping
  public List<User> getAllUsers(HttpServletRequest req) throws Exception {
    
  } 
  @ExceptionHandler(Exception.class)
  public ModelAndView handleError(HttpServletRequest req, Exception ex) {
    logger.error("Request: " + req.getRequestURL() + " raised " + ex);

    ModelAndView mav = new ModelAndView();
    mav.addObject("exception", ex);
    mav.addObject("url", req.getRequestURL());
    mav.setViewName("error");
    return mav;
  }

}

Henceforth, this makes a lot of duplicate code. This is where Controller Advice comes into the picture with an advantage.

Example of Controller Advice

A Controller Advice allows you to use the same exception handling technique across applications, without repeating any code.

Consequently, a class annotated with @ControllerAdvice implements three types of methods:

  • Exception handling method annotated with @ExceptionHandler
  • Model enhancement methods annotated with @ModelAttribute
  • Binder initialization methods annotated with @InitBinder

 


@ControllerAdvice
public class GlobalExceptionHandler
{
   
    @ExceptionHandler(CompanyNotFoundException.class)
    public ModelAndView handleError(HttpServletRequest req, CompanyNotFoundException ex) 
    {
       logger.error("Request: " + req.getRequestURL() + " raised " + ex);

       ModelAndView mav = new ModelAndView();
       mav.addObject("exception", ex);
       mav.addObject("url", req.getRequestURL());
       mav.setViewName("error");
       return mav;
     }

    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity handleUserExceptionError(HttpServletRequest req, HttpServletResponse res, UserNotFoundException ex)
    {
       List errors = Collections.singletonList(ex.getMessage());
       // Get headers
       if(HttpStatus.INTERNAL_SERVER_ERROR.equals(res.getStatus()))
       {
         // do something
       } 

       return new ResponseEntity<>(new ApiError(errors), headers, status);
    }

}

This will allow now to intercept any exception thrown from controllers. This makes implementing exception handling easier.

Conclusion

In this post, I showed how we can implement Controller Advice – exception handler in Spring Boot. This is a very effective way to handle exceptions in the current applications built with Spring Boot.

How To Use AWS Sagemaker

In this post, I will explain how to use AWS Sagemaker. Even if you do not have experience with this AWS service, this guide will help understand AWS Sagemaker step by step.

What is AWS Sagemaker?

AWS Sagemaker is a new web service that AWS offers. It helps to build, train and deploy machine learning models at any scale.  Basically, Sagemaker does the heavy lifting of machine learning and as a developer or data scientist, you can focus on building and training your model.

Major Benefits of AWS Sagemaker

  • You can easily fetch or store data from other AWS Services
  • Highly Scalable. This again relates to my earlier point by being able to connect to other AWS Services.
  • Does heavy lifting of ML algorithms – Fast training

Details Of Machine Learning and Sagemaker

Machine learning is literally machine learning about something. Nevertheless, that something can be anything that humans are usually good at or bad at. Machine Learning provides an ability for systems to learn and improve from experience.

In another way, you can say a system with a feedback loop. A system performs functions, gathers data along the way, uses that data to improve the functions it is performing.

Building a model

Sagemaker makes it easy to connect with AWS Services like S3, Database. Sagemaker also includes Juypter notebooks. These notebooks make it easier to visualize data.

Sagemaker also offers a set of algorithms pre-installed. Sagemaker also comes in with preconfigured TensorFlow or Apache MXNet.

Training and Deploying a model

I will show later in this post how we can train a model in Sagemaker with a single click. The important thing to note here is that you can easily train a model for petabyte-scale in Sagemaker. With continuous improvement, Sagemaker can also improve the performance of the model.

Once you train and tune the model in Sagemaker, it is easy to deploy the model in production. Sagemaker deploys the model on an auto-scaling cluster of EC2 instances.

A simple example of using AWS Sagemaker

  1. Once, you log in to the AWS console, access Sagemaker service. Select Notebook Instances and create a Jupyter Notebook instance as shown below:

How to Use AWS Sagemaker - Notebook Instance

2. On the next page, keep the most default settings as shown. You will need to create an IAM role for S3 bucket creation. If you don’t have that role, you can create it while selecting the role.

3.  Once you select the role, click “create a notebook instance” and it will create a notebook instance. It will take few minutes before it will show it is running. Once the notebook instance is running, click “open” and it will open Jupyter notebook in another tab.

4. Select notebook environment as conda_python3 or anything that you want to use.

Once you have the notebook opened, you can use python or the language of your choice to build a model. For the model, you can easily fetch data from S3 or relational databases from AWS service.

I will not be showing that part in this post. But if you want to refer to a good example, you can visit this post here.

Conclusion

In this post, I showed how one can use AWS Sagemaker to build and train the model for machine learning.

You can subscribe to my blog here.

How to Use API Gateway with Spring Cloud

In this post, I will show how we can use the API Gateway pattern with Spring Cloud. With microservice architecture becoming more and more useful, it has become equally complex how to handle calls to the microservices.

The purpose of microservices is to decouple the application into loosely coupled microservices that can interact with clients and with each other easily.

Importantly, the ease of development and deployment make microservices easier to design based on specific needs.

API Gateway Design Pattern

When the enterprise architecture scales, it becomes complicated with the number of microservices. Clients can directly call these microservices, but there are a few challenges

  1. Each client has to make a request to the exposed microservice API. In many cases, it might have to make multiple server round trips. As a result of this, it increases network latency.
  2. Every microservice has to implement common functionalities like authentication, logging, and authorization.
  3. It becomes harder to change microservice without affecting the client. In reality, the client doesn’t need to know microservice and its implementation behind.

To address these issues, the architecture now contains another layer between the client and the microservices. This is API Gateway.

API Gateway acts like a proxy that routes the request to the appropriate microservices and returns a response to the client. Microservices can also interact with each other through this Gateway.

API Gateway with Spring Cloud

Usage of API Gateway

There are a few functionalities that API Gateway provides.

Routing

The major usage of API Gateway is routing the request from the client to the appropriate server or microservice. Particularly, API Gateway hides the implementation of API from the client.

Common Functionalities

API Gateway can also implement extra common functionalities and in-process reducing the load from microservices.  These common functionalities include logging, authentication, authorization, load balancing, response caching, retry policies, circuit breakers, rate limiter.

Different API Gateways

There are a number of API Gateways available and one can use any of these based on the needs.

  • Netflix API Gateway (Zuul)
  • Amazon API Gateway
  • Mulesoft
  • Kong API Gateway
  • Azure API Gateway

Overall, which API Gateway to use will depend on your use case. But the most of these gateways provide options to scale, flexibility and support.

In this demo, I will be showing how to use spring-cloud-starter-netflix-zuul library for Netflix API Gateway.

Example of API Gateway with Spring Cloud

However, we will develop two microservices. We will also build an API Gateway using Spring Cloud. This API Gateway will act as a reverse proxy to route to either of the microservices.

So let’s create the first microservice. This will contain a CustomerController like below:


@RestController
@RequestMapping("/customer")
public class CustomerController
{
    @GetMapping("/total")
    public List customers()
    {
        List list = new ArrayList<>();
        list.add("Microsoft");
        list.add("Amazon");
        list.add("Apple");
        return list;
    }
}

This microservice will be running on port 8081. server.port=8081.

Now, let’s create another microservice. This will contain VendorController like below:


@RestController
@RequestMapping("/vendor")
public class VendorController
{
    @GetMapping("/total")
    public List vendors()
    {
        List list = new ArrayList<>();
        list.add("CJI Consultants");
        list.add("Signature Consultants");
        list.add("Deloitte");
        return list;
    }
}

This microservice will be running on port 8082. server.port=8082

API Gateway with Spring Cloud

After all, we will create an API Gateway using Spring Cloud. We need to include the following dependencies:

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-webflux'
	implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'io.projectreactor:reactor-test'
}

Note the dependency of spring-cloud-starter-gateway. Nevertheless, we will need a RouteLocator type bean to route our requests. This is where we add the configuration in our Api Gateway.

package com.betterjavacode.apigatewaydemo.config;


import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringCloudConfig
{
    @Bean
    public RouteLocator gatewayRoutes(RouteLocatorBuilder routeLocatorBuilder)
    {
        return routeLocatorBuilder.routes()
                .route("customerModule", rt -> rt.path("/customer/**")
                        .uri("http://localhost:8081/"))
                .route("vendorModule", rt -> rt.path("/vendor/**")
                        .uri("http://localhost:8082/"))
                .build();

    }
}

As shown above, this configuration bean builds a RouteLocator to route requests related to two modules. Also, note that our gateway service is running at port 8080. If a request is initiated with a gateway address, the API gateway will route it to the appropriate service.

Demo

Let’s start out our microservices and API Gateway service. Two microservices are running on ports 8081 and 8082. The API gateway service is running on port 8080.

Now if I access http://localhost:8080/vendor/total, I will get the list of vendors as follows:

API Gateway with Spring Cloud - List of Vendors

If I access http://localhost:8080/customer/total, I will get the list of customers as follows:

API Gateway with Spring Cloud - List of Customers

 

Conclusion

Conclusively, I showed how to use API Gateway with Spring Cloud. API Gateway is an important design concept. With an increasing number of microservices, it becomes important to have a common pattern that can handle a lot of the common workload of these services, and API Gateway helps with that.

My book Simplifying Spring Security is currently on discount sale if you are interested.

Example of Spring Boot Application Authentication with AWS Cognito

In this post, I plan to show an example of Spring Boot Application authentication with AWS Cognito. I will show two flows –

  1. OIDC Authentication
  2. SAML Authentication

AWS Cognito

AWS Cognito is a web service from AWS. Cognito is a user directory as well as an authentication mechanism service. In the enterprise industry, every application has two requirements from a user perspective.

  1. User Directory and Synchronization
  2. User Authentication

Cognito makes this easier by allowing the creation of a user pool or an identity pool. Once you have a pool, you can configure an application with the various settings for authentication. Another major advantage of Cognito is that it offers industry-standard security authentication protocols like OAuth 2.0, OpenID Connect, SAML.

There are equally other features like user access handling, but we won’t go into detail about that as part of this post. In this post, I will further show how we can create a user pool and configure an application to use that user pool.

Spring Boot Application

We will use a simple Spring Boot Application. We will integrate this application with AWS Cognito for authentication.

Spring Boot Setup

Add the following dependencies in the Gradle file


	implementation 'org.springframework.boot:spring-boot-starter-security'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.security:spring-security-oauth2-client'
	implementation 'org.springframework.security:spring-security-oauth2-jose'

And the application.properties will need oauth2 configuration properties:


# ====================================================================================
## SSL Keystore for HTTPS
# ====================================================================================
#security.require-ssl=true
server.port=8743
server.ssl.key-store-password=******
server.ssl.key-store=classpath:sssstore.p12
server.ssl.key-store-type=PKCS12
server.ssl.key-alias=selfsigned_localhost
# =====================================================================================
spring.security.oauth2.client.registration.cognito.clientId=7mtivo8jobdtd6dvocldkmu6qk
spring.security.oauth2.client.registration.cognito.clientSecret=*********
spring.security.oauth2.client.registration.cognito.scope=openid
spring.security.oauth2.client.registration.cognito.redirect-uri=https://localhost:8743/login/oauth2/code/cognito
spring.security.oauth2.client.registration.cognito.clientName=SpringBootCognitoDemo
spring.security.oauth2.client.registration.cognito.authorization-grant-type=authorization_code
spring.security.oauth2.client.provider.cognito.issueUri=https://cognito-idp.{awszone}.amazonaws.com/{userPoolId}
spring.security.oauth2.client.provider.cognito.authorization-uri=https://{customDomain}.auth.{awszone}.amazoncognito.com/oauth2/authorize
spring.security.oauth2.client.provider.cognito.token-uri=https://{customDomain}.auth.{awszone}.amazoncognito.com/oauth2/token
spring.security.oauth2.client.provider.cognito.jwk-set-uri=https://cognito-idp.{awszone}.amazonaws.com/{userPoolId}/.well-known/jwks.json
spring.security.oauth2.client.provider.cognito.user-name-attribute= cognito:username

Security Configuration

Our Main Controller class will look like below:



@Controller
public class MainController
{
    @GetMapping("/")
    public String home(Model model, Principal principal)
    {
        model.addAttribute("username", principal.getName());
        return "index";
    }
}

So when we will access our application, it will redirect to AWS Cognito UI for authentication. Based on the selected flow OIDC or SAML, the authentication will happen.

SecurityConfig will be as below:


@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
    @Autowired
    public CustomLogoutSuccessHandler customLogoutSuccessHandler;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception
    {
        httpSecurity.csrf()
                .and()
                .authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .oauth2Login()
                .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler(customLogoutSuccessHandler)
                ;

    }
}

Our simple index template will look like below:


<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <meta content="width=device-width, initial-scale=1" name="viewport">
    <title>Cognito Demo</title>
</head>
<body>
<div class="container">
    <h1 class="title">OAuth 2.0 Spring Security Cognito Demo</h1>
    <div>
        <div class="box">
            Hello, <strong th:text="${username}"></strong>!
        </div>
        <a th:href="@{/logout}" class="btn btn-primary">Logout</a>
    </div>
</div>
</body>
</html>

 

Create a User Pool

Once you log in to AWS Console, select Cognito as AWS Service. The first screen will show you two options – Create a user pool and Create an identity pool.

We will select Create a user pool.

Authentication with AWS Cognito - Create User Pool

Enter a suitable name for your user pool and select Step through settings. This will allow us to modify any settings that we don’t want to use for our use case.

Now select user pool attributes for users. These attributes allow us to identify the user and what a user can enter during authentication.

Authentication with AWS Cognito - User Attributes

For our demo, we have selected Email address, family name, and given name as attributes. So the user will log in to the application using an email address.

For password policy, we have set that password should be of minimum length 8, should be alphanumeric with upper case and lower case characters.

Authentication with AWS Cognito - Password Policy

Most of the other settings we will choose as default. For App Clients Step, we will choose username and password-based authentication for Auth Flow.

Authentication with AWS Cognito - App Clients

Application Client Configuration

Once we create a user pool, we will continue to step through the next set of configurations and that includes the Application Client. In this configuration, we configure our application that will be using the user pool.

We need to enable identity provider and Cognito User Pool.

Authentication with AWS Cognito - App Client Settings

Configuring Federation Identity Provider

As mentioned previously, we will also configure a SAML Identity Provider for authentication. In this case, the flow will be application -> AWS Cognito -> SAML Identity Provider.

Configure Application in Okta

For this demo, we will use Okta as SAML Identity Provider. Log in to Okta Administrator console , configure a new application for SAML Integration as below:

Configure SAML Okta

On the next step, provide Single Sign On URL and this will be https://{yourcustomdomainfromcognito}.auth.{awszone}.amazoncognito.com. We also need to provide Audience URI (SP Entity ID). In this case, Cognito will act as Service Provider to Okta.

 Okta SAML Settings

We also need to configure Attribute Statements as shown above. Those are the attributes we have configured in our Cognito User Attributes – email, given_name, family_name.

Basically, make sure that the user you plan to use is in Okta Users Directory. It will be the same user that we will need to be either configured in Cognito User Pool or created through sign-up.

Configure Federated Identity Provider in Cognito

  • In our user pool configuration, select Federated Identity Provider.
  • Choose the option of SAML and Cognito will show you to upload a metadata file or metadata URL for Identity Provider. In our case, it will be https://dev-19753289.okta.com/app/exkmt322q1Kl15Rsk5d6/sso/saml/metadata.
  • Type a name for Federated Identity Provider and Save the changes.

Therefore, if we go back to App Client Settings, we should see an option to select Federated Identity Provider. With the new configuration, our settings will look like below:

Cognito Federated Identity Provider

Anyhow, make sure the user attributes(given_name, family_name, and email) in Cognito are editable.

However, we have completed our configuration. Now we can show the demo.

Demo of Spring Boot Application Authentication with AWS Cognito

Now if I run my application and access it at https://localhost:8743/, I will see the following screen to select an identity provider to authenticate with:

Home Realm Discovery Cognito

Nevertheless, we can use Okta or Cognito User Pool to log in.

SAML Flow

  1. User accesses the application that sends Authorization Code Flow OAuth request to Cognito
  2. Cognito sends SAML Request to Okta.
  3. The user enters credentials on the okta login screen.
  4. Okta sends the SAML response back to Cognito at endpoint https://{customdomain}.auth.{awszone}.amazoncognito.com/saml2/idpresponse
  5. Cognito processes the saml response and generates auth code and response back to the application.
  6. The application uses auth code to get token from Cognito and authenticates the user on verification of token.

Above all, after successful authentication, the user will see

Okta SAML Authentication

Instead of Okta, we had chosen Cognito login on the same Home Realm Discovery screen, we will see the following after successful authentication:

Cognito Successful Authentication

Conclusion

In this post, I showed how we can use AWS Cognito for authentication with Spring Boot application. Moreover, Cognito helps in configuring users as well as removing boilerplate code of security flows.

In conclusion, if you have not bought my ebook Simplifying Spring Security, it is available here.