Easily Manage Properties in your Java / Spring application

Question to the Java developers out there: properties are easy, right? All you need to do is to type up some key/value pairs in a simple file, read that file in when your application starts up (Java makes that a no-brainer), and refer to the appropriate property in your application code. If you’re using Spring, it’s even easier; just configure your beans in your app context XML file.

Well, that’s fine for a simple application that you’re deploying to your local test server. But for a production ready app, things are a little more complication. There are typically two things you’ll need to be able to deal with:

  1. You’ll like have different servers–test, staging, production, etc–each with their own different properties. For example, your test server will likely have different database credentials and connection URLs than your production server.
  2. You won’t want to store all of your properties in plaintext.
What we need is a way to separate the properties that differ across environments. Furthermore, we want to be able to encrypt some of these properties in case our application falls into the wrong hands.

Separate properties by environment

Let’s look at how we’d typically set properties in a Spring web application. For our purposes here, we’ll focus on setting our datasource’s properties: username, password, url, and the JDBC driver class name.
These would typically be set in your spring config file like so:
<bean id=“dataSource”
class=“org.springframework.jdbc.datasource.DriverManagerDataSource”>
<property name=“driverClassName” value=“com.mysql.jdbc.Driver” />
<property name=“url” value=“jdbc:mysql://127.0.0.1:3366/my_db” />
<property name=“username” value=“joe” />
<property name=“password” value=“foobarbaz” />
</bean>
Of course, any of these properties might change as you deploy to a different environment. Your production database might be running on its own server, for example. And you certainly wouldn’t want to use the same credentials in production as you do on your test server… right?
So our first step is to externalize these properties into a separate file. Let’s create a file called db.properties. This file should go somewhere in our classpath; for example, in com/mycompany/config in your compile target directory path. This file will contain our data source values, like so:
db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://127.0.0.1:3366/my_db
db.username=joe
db.password= foobarbaz
Now, how can we use this properties file? For this, Spring provides a handy class, org.springframework.beans.factory.config.PropertyPlaceholderConfigurer. This class takes a list of property file locations. It will read all of the files in the list, and store their properties to use as placeholder replacements.
To use a PropertyPlaceholderConfigurer, simply declare it as a bean:
<bean id=“propertyPlaceholderConfigurer”
class=org.springframework.beans.factory.config.PropertyPlaceholderConfigurer>
<property name=“locations”>
<list>
<value>classpath:com/mycompany/config/db.properties
</value>
</list>
</property>
</bean>
Now we can replace are hard-coded properties in our spring config file with these placeholders:
<bean id=“dataSource”
class=“org.springframework.jdbc.datasource.DriverManagerDataSource”>
<property name=“driverClassName” value=${db.driver}/>
<property name=“url” value=${db.url}/>
<property name=“username” value=${db.username}/>
<property name=“password” value=${db.password}/>
</bean>
At runtime, Spring will replace the ${…} tokens with their property values.
That works great, but what we really want is separate config files, one per environment. To do this, we copy our db.properties file to db-dev.properties and db-prod.properties (this assumes that we have two environments, development and production; of course, make as many copies as you have distinct environments.) Those files should of course continue to reside on our classpath. And of course, the values of each of the four properties should be changed to match the data source settings for the specific environment.
At runtime, we’ll want Spring to read the current environment’s db properties file. Fortunately, when it performs placeholder replacements, Spring will look for values not just read in by the PropertyPlaceholderConfigurer, but also system properties and VM arguments as well. So what we can do is to pass a VM argument on our startup script. This value will define the current environment. So in our dev environment we’d add this to our startup script:
    -Dcom.mycompany.env=dev
while in production we’d add this:
    -Dcom.mycompany.env=prod
Last, we adjust our PropertyPlaceholderConfigurer location to tell it which configuration file to read in.
<bean id=“propertyPlaceholderConfigurer”
class=org.springframework.beans.factory.config.PropertyPlaceholderConfigurer>
<property name=“locations”>
<list>
<value>classpath:com/mycompany/config/db-${com.mycompany.env}.properties
</value>
</list>
</property>
</bean>
The highlighted token above will be replaced with the value of com.mycompany.env set in our startup script. As a result, our PropertyPlaceholderConfigurer will read in the environment-appropriate file, and our dataSource bean will be populated with the environment-appropriate values.

Encrypt your credentials

Our second goal is to keep from bundling our database credentials in plaintext when we package and distribute our web application. To do this, we are going to override Spring’s PropertyPlaceholderConfigurer with our own implementation. This implementation will do one additional thing: when it encounters a property whose name ends with “-enc”, it will assume that the value is encrypted, and therefore decrypt the value just after reading it in.
First, let’s assume that we have written a class called DataEncrypter, containing two methods:
public String encrypt(String rawValue);

public String decrypt(String encryptedValue);

Their functions should be obvious; encrypt() takes a plaintext String and converts it to an encrypted String; decrypt() reverses the process. Both methods should of course rely on the same key(s). This tutorial will skip what an actual implementation of each method might look like; that’s for the security experts to provide. For information, look up information about the javax.crypto package. Instead, we’ll assume that a good implementation has been written.
The first thing we’ll want to do to encrypt our database credentials with our DataEncrypter instance, and put those encrypted values into our properties files. The most straightforward way to do this is to simply create a Java class with a main(String[] args) method, which uses our DataEncrypter to encrypt a value passed as args[0]:
public static void main(String[] args) {
DataEncrypter encrypter = …; // obtain an instance
System.out.println( encrypter.encrypt(args[0]) );
}
Run that class once per property that you want to encrypt; alternatively, you can modify it to expect multiple properties in args, and encrypt them all in one run. Next, we’ll swap those values in to the properties file in place of their plaintext version. We’ll also rename to property with an “-enc” suffix.
db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://127.0.0.1:3366/my_db
db.username-enc=f7BjAyDkWSs=
db.password-enc=4Q7xTCr5hZC9Ms6iTSjG3Q==
So how will those values be decrypted? We’ll need to create our own subclass of PropertyPlaceholderConfigurer (which we’ll call DecryptingPropertyPlaceholderConfigurer). PropertyPlaceholderConfigurer contains one important method that we’ll want to override:
protected void convertProperties(Properties props)
This method read in the properties from any file found in its list of file locations. We’ll want to look for any properties whose name ends with “-enc”, and invoke our DataEncrypter’s decrypt() method:
public class DecryptingPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
private static final String SUFFIX_ENC = “-enc”;
private final DataEncrypter encrypter = …; // obtain an instance
private final Logger log = LoggerFactory.getLogger (getClass ());
@Override
protected void convertProperties(Properties props) {
// props refers to the contents of a single properties file
Enumeration<?> propertyNames = props.propertyNames();
while (propertyNames.hasMoreElements()) {
String propertyName = (String)propertyNames.nextElement();
String propertyValue = props.getProperty(propertyName);
// look for names such as password-enc
if (propertyName.endsWith(SUFFIX_ENC)) {
try {
String decValue = encrypter.decrypt(propertyValue);
propertyValue = decValue;
// normalize the property name by 
// removing the “enc-” prefix
propertyName = propertyName.substring(
0, propertyName.length() – SUFFIX_ENC());
} catch (EncryptionException e) {
log.error( String.format(
  “Unable to decode %s = %s”, propertyName, propertyValue), e);
throw new RuntimeException();
}
}
props.setProperty(propertyName, propertyValue);
}
}
}
Note that we strip the “-enc” suffix from each encoded property that is encountered. That we, we can continue to refer to our data source password, for example, as db.password rather than db.enc-password. Note also that if we encounter an error decrypting a property, we log the issue and throw a RuntimeException. This is generally the correct thing to do; we don’t want our application to run with partially-incorrect properties. One thing to note here is that you might want to remove the logging of the propertyValue. A common encryption problem is one where someone forgot to encrypt the value before adding it to the properties file. In such a case, you probably would not want the plaintext value hanging out in your log file.
The last thing to do is simply plug our subclass into our Spring config file:
<bean id=“propertyPlaceholderConfigurer”
class=“com.mycompany.DecryptingPropertyPlaceholderConfigurer”>
<property name=“locations”>
<list>
<value>classpath:com/mycompany/config/db-${com.mycompany.env}.properties
</value>
</list>
</property>
</bean>
Now, an important caveat is that this is not an end-all, be-all solution to securing your credentials. This approach is only as strong as your encryption algorithm, and it will never ensure 100% security. It’s not a substitute for simply ensuring that your source code and your webapp bundle remain only in your hands (or for taking other measures such as restricting access to your database server, not reusing passwords, etc). But should someone else get get ahold of your credentials, this approach should at least buy you enough time to change your credentials to prevent unauthorized access.

Done!

Now that you’ve set up your infrastructure, it should be easy to see how to set additional properties, whether in the same files we’d created above or in new files that you might create. Managing properties is once again very easy, but now it’s also very flexible and a tad more secure than it was before.

Leave a Reply

Your email address will not be published. Required fields are marked *