Secure Spring Boot REST APIs with Amazon Cognito
In this tutorial, you will learn how to secure Spring Boot REST APIs with Amazon Cognito.
Amazon Cognito is an access management service that helps to secure your web and mobile applications easily and quickly.
Follow the steps below to complete this tutorial:
Setup Cognito User Pool
The first thing you'll need to do is, create and setup a user pool in AWS Cognito. Go to our Amazon Cognito User Pool Setup tutorial to create and configure a user pool for your application.
Create a Spring Boot Application
- Go to Spring Initializr at https://start.spring.io and create a Spring Boot application with details as follows:
- Project: Choose Gradle Project or Maven Project.
- Language: Java
- Spring Boot: Latest stable version of Spring Boot is selected by default. So leave it as is.
- Project Metadata: Provide group name in the Group field. The group name is the id of the project. In Artifact field, provide the name of your project. In the package field, provide package name for your project. Next, select your preferred version of Java that is installed on your computer and is available on your local environment.
- Dependencies: Add dependencies for Spring Web, Spring Boot DevTools, Spring Security, and OAuth2 Resource Server.
- Click the GENERATE button and save/download the project zip bundle.
- Extract the project to your preferred working directory.
- Import the project in your preferred Java development IDE such as Eclipse or IntelliJ IDEA.
Refer to the image below for example:

Add Dependency
Add Amazon Cognito Java SDK dependency to the project.
For Gradle
Open the default build.gradle file and add the gradle dependency for Amazon Cognito Java SDK:
implementation group: 'com.amazonaws', name: 'aws-java-sdk-cognitoidp', version: '1.11.1019'
For Maven
Add the following dependency of Amazon Cognito Java SDK to pom.xml file:
Find the latest version of AWS Cognito Java SDK in the Amazon Cognito Java SDK Maven Repository.
Add Application Configurations
Open the application.properties file and add the following configuration to it. Do not forget to replace the configuration values that is relevant to your project.
server.port=5000
#aws
aws.access-key = AKIASI5DFFY2KVL46ONH
aws.access-secret = +sYwUXMeHFUDqI/YvJNWoMAlzYnWA75qEGw06jTMG
aws.default-region = us-east-1
#cognito user pool
aws.cognito.clientId=2binakrnvlaka8ait2l3l8bneo
aws.cognito.userPoolId=us-east-1_mLMX4T314
aws.cognito.region=us-east-1
aws.cognito.connectionTimeout=2000
aws.cognito.readTimeout=2000
aws.cognito.jwk = https://cognito-idp.us-east-1.amazonaws.com/us-east-1_mLMX4T314/.well-known/jwks.json
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://cognito-idp.us-east-1.amazonaws.com/us-east-1_mLMX4T314
logging.level.org.springframework=INFO
logging.level.com.example=INFO
# Logging pattern for console
logging.pattern.console= %d{yyyy-MM-dd HH:mm:ss} - %msg%n
Create a SecurityConfig class
This SecurityConfig class file must extend the WebSecurityConfigurerAdapter abstract class.
The SecurityConfig class must be annotated with the following annotations:
- @Configuration - This annotation indicates that the class is a configuration class containing bean definitions for the application context.
- @EnableWebSecurity - This annotation indicates that the class is a Spring Security configuration with information telling how to authenticate users. It provides security configuration via HttpSecurity which is provided as a method parameter in a method called configure and allows you to configure accessibility based on the url-patterns, handlers and authentication endpoints.
- @EnableGlobalMethodSecurity(prePostEnabled = true) - This annotation enables Spring Security global method security. The use of prePostEnabled = true enables @PreAuthorize and @PostAuthorize annotations.
package com.sample.cognito.security;
import java.util.Arrays;
import java.util.List;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
public static final String SIGNUP_URL = "/api/users/sign-up";
public static final String SIGNIN_URL = "/api/users/sign-in";
@Override
protected void configure(HttpSecurity http) throws Exception {
List<String> permitAllEndpointList = Arrays.asList(SIGNUP_URL, SIGNIN_URL);
http.cors().and().csrf().disable()
.authorizeRequests(expressionInterceptUrlRegistry -> expressionInterceptUrlRegistry
.antMatchers(permitAllEndpointList
.toArray(new String[permitAllEndpointList.size()]))
.permitAll().anyRequest().authenticated())
.oauth2ResourceServer().jwt();
}
}
Create a CognitoConfig class
We need to create a bean method returning AWSCognitoIdentityProvider interface for accessing Amazon Cognito Identity Provider. The following code show how to do so:
package com.sample.cognito.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.cognitoidp.AWSCognitoIdentityProvider;
import com.amazonaws.services.cognitoidp.AWSCognitoIdentityProviderClientBuilder;
@Configuration
public class CognitoConfig {
@Value(value = "${aws.access-key}")
private String accessKey;
@Value(value = "${aws.access-secret}")
private String secretKey;
@Bean
public AWSCognitoIdentityProvider cognitoClient() {
BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey);
return AWSCognitoIdentityProviderClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(awsCreds)).withRegion("us-east-1")
.build();
}
}
Create POJO classes
Create the following POJO classes for transferring data in requests and responses of APIs:
package com.sample.cognito.model;
public class UserSignUpRequest {
private String username;
private String email;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
package com.sample.cognito.model;
public class UserSignInRequest {
private String username;
private String email;
private String password;
private String newPassword;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getNewPassword() {
return newPassword;
}
public void setNewPassword(String newPassword) {
this.newPassword = newPassword;
}
}
package com.sample.cognito.model;
public class UserSignInResponse {
private String accessToken;
private String idToken;
private String refreshToken;
private String tokenType;
private Integer expiresIn;
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public String getIdToken() {
return idToken;
}
public void setIdToken(String idToken) {
this.idToken = idToken;
}
public String getRefreshToken() {
return refreshToken;
}
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
public String getTokenType() {
return tokenType;
}
public void setTokenType(String tokenType) {
this.tokenType = tokenType;
}
public Integer getExpiresIn() {
return expiresIn;
}
public void setExpiresIn(Integer expiresIn) {
this.expiresIn = expiresIn;
}
}
package com.sample.cognito.model;
public class UserDetail {
private String firstName;
private String lastName;
private String email;
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 String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
Create Custom Exception class
package com.sample.cognito.exception;
public class CustomException extends RuntimeException {
private static final long serialVersionUID = 1L;
public CustomException(String message) {
super(message);
}
public CustomException(String message, Throwable cause) {
super(message, cause);
}
}
Create Web Controller
Lets create a web controller with REST APIs to sign up, sign in users in Cognito and also a protected GET method with path api/users/detail. The following code shows how to do so:
package com.sample.cognito.controller;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.amazonaws.services.cognitoidp.AWSCognitoIdentityProvider;
import com.amazonaws.services.cognitoidp.model.AWSCognitoIdentityProviderException;
import com.amazonaws.services.cognitoidp.model.AdminCreateUserRequest;
import com.amazonaws.services.cognitoidp.model.AdminCreateUserResult;
import com.amazonaws.services.cognitoidp.model.AdminInitiateAuthRequest;
import com.amazonaws.services.cognitoidp.model.AdminInitiateAuthResult;
import com.amazonaws.services.cognitoidp.model.AdminRespondToAuthChallengeRequest;
import com.amazonaws.services.cognitoidp.model.AdminRespondToAuthChallengeResult;
import com.amazonaws.services.cognitoidp.model.AdminSetUserPasswordRequest;
import com.amazonaws.services.cognitoidp.model.AttributeType;
import com.amazonaws.services.cognitoidp.model.AuthFlowType;
import com.amazonaws.services.cognitoidp.model.AuthenticationResultType;
import com.amazonaws.services.cognitoidp.model.ChallengeNameType;
import com.amazonaws.services.cognitoidp.model.ChangePasswordRequest;
import com.amazonaws.services.cognitoidp.model.DeliveryMediumType;
import com.amazonaws.services.cognitoidp.model.InvalidParameterException;
import com.amazonaws.services.cognitoidp.model.MessageActionType;
import com.sample.cognito.exception.CustomException;
import com.sample.cognito.model.UserDetail;
import com.sample.cognito.model.UserSignInRequest;
import com.sample.cognito.model.UserSignInResponse;
import com.sample.cognito.model.UserSignUpRequest;
@RestController
@RequestMapping(path = "/api/users")
public class UserController {
@Autowired
private AWSCognitoIdentityProvider cognitoClient;
@Value(value = "${aws.cognito.userPoolId}")
private String userPoolId;
@Value(value = "${aws.cognito.clientId}")
private String clientId;
@PostMapping(path = "/sign-up")
public void signUp(@RequestBody UserSignUpRequest userSignUpRequest) {
try {
AttributeType emailAttr =
new AttributeType().withName("email").withValue(userSignUpRequest.getEmail());
AttributeType emailVerifiedAttr =
new AttributeType().withName("email_verified").withValue("true");
AdminCreateUserRequest userRequest = new AdminCreateUserRequest()
.withUserPoolId(userPoolId).withUsername(userSignUpRequest.getUsername())
.withTemporaryPassword(userSignUpRequest.getPassword())
.withUserAttributes(emailAttr, emailVerifiedAttr)
.withMessageAction(MessageActionType.SUPPRESS)
.withDesiredDeliveryMediums(DeliveryMediumType.EMAIL);
AdminCreateUserResult createUserResult = cognitoClient.adminCreateUser(userRequest);
System.out.println("User " + createUserResult.getUser().getUsername()
+ " is created. Status: " + createUserResult.getUser().getUserStatus());
// Disable force change password during first login
AdminSetUserPasswordRequest adminSetUserPasswordRequest =
new AdminSetUserPasswordRequest().withUsername(userSignUpRequest.getUsername())
.withUserPoolId(userPoolId)
.withPassword(userSignUpRequest.getPassword()).withPermanent(true);
cognitoClient.adminSetUserPassword(adminSetUserPasswordRequest);
} catch (AWSCognitoIdentityProviderException e) {
System.out.println(e.getErrorMessage());
} catch (Exception e) {
System.out.println("Setting user password");
}
}
@PostMapping(path = "/sign-in")
public @ResponseBody UserSignInResponse signIn(
@RequestBody UserSignInRequest userSignInRequest) {
UserSignInResponse userSignInResponse = new UserSignInResponse();
final Map<String, String> authParams = new HashMap<>();
authParams.put("USERNAME", userSignInRequest.getUsername());
authParams.put("PASSWORD", userSignInRequest.getPassword());
final AdminInitiateAuthRequest authRequest = new AdminInitiateAuthRequest();
authRequest.withAuthFlow(AuthFlowType.ADMIN_NO_SRP_AUTH).withClientId(clientId)
.withUserPoolId(userPoolId).withAuthParameters(authParams);
try {
AdminInitiateAuthResult result = cognitoClient.adminInitiateAuth(authRequest);
AuthenticationResultType authenticationResult = null;
if (result.getChallengeName() != null && !result.getChallengeName().isEmpty()) {
System.out.println("Challenge Name is " + result.getChallengeName());
if (result.getChallengeName().contentEquals("NEW_PASSWORD_REQUIRED")) {
if (userSignInRequest.getPassword() == null) {
throw new CustomException(
"User must change password " + result.getChallengeName());
} else {
final Map<String, String> challengeResponses = new HashMap<>();
challengeResponses.put("USERNAME", userSignInRequest.getUsername());
challengeResponses.put("PASSWORD", userSignInRequest.getPassword());
// add new password
challengeResponses.put("NEW_PASSWORD", userSignInRequest.getNewPassword());
final AdminRespondToAuthChallengeRequest request =
new AdminRespondToAuthChallengeRequest()
.withChallengeName(ChallengeNameType.NEW_PASSWORD_REQUIRED)
.withChallengeResponses(challengeResponses)
.withClientId(clientId).withUserPoolId(userPoolId)
.withSession(result.getSession());
AdminRespondToAuthChallengeResult resultChallenge =
cognitoClient.adminRespondToAuthChallenge(request);
authenticationResult = resultChallenge.getAuthenticationResult();
userSignInResponse.setAccessToken(authenticationResult.getAccessToken());
userSignInResponse.setIdToken(authenticationResult.getIdToken());
userSignInResponse.setRefreshToken(authenticationResult.getRefreshToken());
userSignInResponse.setExpiresIn(authenticationResult.getExpiresIn());
userSignInResponse.setTokenType(authenticationResult.getTokenType());
}
} else {
throw new CustomException(
"User has other challenge " + result.getChallengeName());
}
} else {
System.out.println("User has no challenge");
authenticationResult = result.getAuthenticationResult();
userSignInResponse.setAccessToken(authenticationResult.getAccessToken());
userSignInResponse.setIdToken(authenticationResult.getIdToken());
userSignInResponse.setRefreshToken(authenticationResult.getRefreshToken());
userSignInResponse.setExpiresIn(authenticationResult.getExpiresIn());
userSignInResponse.setTokenType(authenticationResult.getTokenType());
}
} catch (InvalidParameterException e) {
throw new CustomException(e.getErrorMessage());
} catch (Exception e) {
throw new CustomException(e.getMessage());
}
cognitoClient.shutdown();
return userSignInResponse;
}
@GetMapping(path = "/detail")
public @ResponseBody UserDetail getUserDetail() {
UserDetail userDetail = new UserDetail();
userDetail.setFirstName("Test");
userDetail.setLastName("Buddy");
userDetail.setEmail("testbud[email protected]");
return userDetail;
}
}
Test your Application
Run your application and test the REST APIs: