Working with Spring Boot Application to Store & Retrieve Secrets via AWS Secret Manager

Dumindu Yapa, MSc
4 min readAug 24, 2020

--

Managing and storing credentials in a well-organized structure is a must for a software application. AWS Secret Manager is an AWS service that addresses it easier to do that for you. Especially database credentials, passwords, third-party API keys, and texts can be stored & retrieved by the secret manager.

Let’s focus on third-party API keys option for this discussion since the first two options are dedicated to database credentials.

Solution

  • A user creates secrets in AWS Secret Manager.
  • Spring Boot application retrieves the API keys stored in AWS Secret Manager by using the secret name and its region.
  • The application will connect to the MongoDB database first and other necessary components applying the credential it retrieves from AWS Secret Manager using AWS Java SDK.

Let’s get to the following steps we need to cover this whole scenario.

Step 1: Create & Store secrets in AWS Secret Manager.

(We are not discussing this step here. You can go through this link to configure it within AWS Secret Manager.

Please select the “Other types of secrets” option when you store your secrets)

Step 2: Retrieving your secrets from the AWS Secret Manager.

  1. Add below dependency to the pom.xml file.

2. Apply an application listener class to retrieve secrets from the Java side.

The secret values I’m retrieving from AWS Secret manager will be used to update my application.properties file. This will set the application context with new properties before loading any other beans.

getSecret() method :

You can have this code snippet when you apply secret values through AWS Secret Manager.

Application Prepared Event :

This event is triggered just before the refresh is started but after bean definitions have been loaded. I’m using this event since my application needs to log into the MongoDB database right after the application starts.

3. Register the properties listener class as a listener in the Main method.

Since Application-Prepared-Event triggered before the Application Context is created, we cannot register as a @Bean. You can register it as the following two methods.

  1. SpringApplication.addListners(…)
  2. SpringApplicationBuilder.listners(…)

If you want those listeners to be registered automatically, you can add a META-INF/spring.factories file to your project and reference your listener.

Refer: https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-application-events-and-listeners

Enjoy :)

Sample Codes:

PropertiesListener.java

@Component("PropertiesListener")
public class PropertiesListener implements ApplicationListener<ApplicationPreparedEvent> {
private static final Logger LOGGER = LoggerFactory.getLogger(PropertiesListener.class);private ObjectMapper mapper = new ObjectMapper();private static final String AWS_SECRET_NAME = "";
private static final String AWS_REGION = "";
private static final String SPRING_DATASOURCE_USERNAME = "";
private static final String SPRING_DATASOURCE_URI = "";
@Override
public void onApplicationEvent(ApplicationPreparedEvent event) {
// Get username and password from AWS Secret Manager using secret name
String secretJson = getSecret();
String database = getString(secretJson, SPRING_DATASOURCE_USERNAME);
String uri = getString(secretJson, SPRING_DATASOURCE_URI);
ConfigurableEnvironment environment = event.getApplicationContext().getEnvironment();
Properties props = new Properties();
props.put(SPRING_DATASOURCE_USERNAME, database);
props.put(SPRING_DATASOURCE_URI, uri);
environment.getPropertySources().addFirst(new PropertiesPropertySource("aws.secret.manager", props));}private String getSecret() {// Create a Secrets Manager client
AWSSecretsManager client = AWSSecretsManagerClientBuilder.standard().withRegion(AWS_REGION).build();
String secret = null;
GetSecretValueRequest getSecretValueRequest = new GetSecretValueRequest().withSecretId(AWS_SECRET_NAME);
GetSecretValueResult getSecretValueResult = null;
try {getSecretValueResult = client.getSecretValue(getSecretValueRequest);// Decrypts secret using the associated KMS CMK.
// Depending on whether the secret is a string or binary, one of these fields
// will be populated.
if (getSecretValueResult != null && getSecretValueResult.getSecretString() != null) {
secret = getSecretValueResult.getSecretString();
}
} catch (DecryptionFailureException | InternalServiceErrorException | InvalidParameterException
| InvalidRequestException | ResourceNotFoundException e) {
LOGGER.error(e.getMessage(), e);
return null;
}return secret;}private String getString(String json, String path) {try {JsonNode root = mapper.readTree(json);
return root.path(path).asText();
} catch (IOException e) {LOGGER.error(e.getMessage(), e);
return null;
}
}
}

Main.java

@SpringBootApplication
public class Main extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Main.class);
app.addListeners(new PropertiesListener());
app.run(args);
}
}

--

--

Dumindu Yapa, MSc
Dumindu Yapa, MSc

No responses yet