This article by Peter Mularien is an excerpt from the book Spring Security 3.
In this article, we will:
(For more resources on Spring, see here.)
In any secured system, password security is a critical aspect of trust and authoritativeness of an authenticated principal. Designers of a fully secured system must ensure that passwords are stored in a way in which malicious users would have an impractically difficult time compromising them.
The following general rules should be applied to passwords stored in a database:
For the purposes of most applications, the best fit for these requirements involves one-way encoding or encryption of passwords as well as some type of randomization of the encrypted passwords. One-way encoding provides the security and uniqueness properties that are important to properly authenticate users with the added bonus that once encrypted, the password cannot be decrypted.
In most secure application designs, it is neither required nor desirable to ever retrieve the user's actual password upon request, as providing the user's password to them without proper additional credentials could present a major security risk. Most applications instead provide the user the ability to reset their password, either by presenting additional credentials (such as their social security number, date of birth, tax ID, or other personal information), or through an email-based system.
Storing other types of sensitive information Many of the guidelines listed that apply to passwords apply equally to other types of sensitive information, including social security numbers and credit card information (although, depending on the application, some of these may require the ability to decrypt). It's quite common for databases storing this type of information to represent it in multiple ways, for example, a customer's full 16-digit credit card number would be stored in a highly encrypted form, but the last four digits might be stored in cleartext (for reference, think of any internet commerce site that displays XXXX XXXX XXXX 1234 to help you identify your stored credit cards).
You may already be thinking ahead and wondering, given our (admittedly unrealistic) approach of using SQL to populate our HSQL database with users, how do we encode the passwords? HSQL, or most other databases for that matter, don't offer encryption methods as built-in database functions.
Typically, the bootstrap process (populating a system with initial users and data) is handled through some combination of SQL loads and Java code. Depending on the complexity of your application, this process can get very complicated.
For the JBCP Pets application, we'll retain the embedded-database declaration and the corresponding SQL, and then add a small bit of Java to fire after the initial load to encrypt all the passwords in the database. For password encryption to work properly, two actors must use password encryption in synchronization ensuring that the passwords are treated and validated consistently.
Password encryption in Spring Security is encapsulated and defined by implementations of the o.s.s.authentication.encoding.PasswordEncoder interface. Simple configuration of a password encoder is possible through the <password-encoder> declaration within the <authentication-provider> element as follows:
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="jdbcUserService">
<password-encoder hash="sha"/>
</authentication-provider>
</authentication-manager>
You'll be happy to learn that Spring Security ships with a number of implementations of PasswordEncoder, which are applicable for different needs and security requirements. The implementation used can be specified using the hash attribute of the <password-encoder> declaration.
The following table provides a list of the out of the box implementation classes and their benefits. Note that all implementations reside in the o.s.s.authentication. encoding package.
Implementation class
|
Description
|
hash value
|
PlaintextPasswordEncoder
|
Encodes the password
as plaintext. Default
DaoAuthenticationProvider
password encoder.
|
plaintext
|
Md4PasswordEncoder
|
PasswordEncoder utilizing the
MD4 hash algorithm. MD4 is not
a secure algorithm-use of this
encoder is not recommended.
|
md4
|
Md5PasswordEncoder
|
PasswordEncoder utilizing the
MD5 one-way encoding algorithm.
|
md5
|
ShaPasswordEncoder
|
PasswordEncoder utilizing the SHA
one-way encoding algorithm. This
encoder can support confi gurable
levels of encoding strength.
|
Sha
sha-256
|
LdapShaPasswordEncoder
|
Implementation of LDAP SHA
and LDAP SSHA algorithms
used in integration with LDAP
authentication stores.
|
{sha}
{ssha}
|
As with many other areas of Spring Security, it's also possible to reference a bean definition implementing PasswordEncoder to provide more precise configuration and allow the PasswordEncoder to be wired into other beans through dependency injection. For JBCP Pets, we'll need to use this bean reference method in order to encode the bootstrapped user data.
Let's walk through the process of configuring basic password encoding for the JBCP Pets application.
Configuring basic password encoding involves two pieces—encrypting the passwords we load into the database after the SQL script executes, and ensuring that the DaoAuthenticationProvider is configured to work with a PasswordEncoder.
First, we'll declare an instance of a PasswordEncoder as a normal Spring bean:
<bean class="org.springframework.security.authentication.
encoding.ShaPasswordEncoder" id="passwordEncoder"/>
You'll note that we're using the SHA-1 PasswordEncoder implementation. This is an efficient one-way encryption algorithm, commonly used for password storage.
We'll need to configure the DaoAuthenticationProvider to have a reference to the PasswordEncoder, so that it can encode and compare the presented password during user login. Simply add a <password-encoder> declaration and refer to the bean ID we defined in the previous step:
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="jdbcUserService">
<password-encoder ref="passwordEncoder"/>
</authentication-provider>
</authentication-manager>
Try to start the application at this point, and then try to log in. You'll notice that what were previously valid login credentials are now being rejected. This is because the passwords stored in the database (loaded with the bootstrap test-users-groupsdata. sql script) are not stored in an encrypted form that matches the password encoder. We'll need to post-process the bootstrap data with some simple Java code.
The approach we'll take for encoding the passwords loaded via SQL is to have a Spring bean that executes an init method after the embedded-database bean is instantiated. The code for this bean, com.packtpub.springsecurity.security. DatabasePasswordSecurerBean, is fairly simple.
public class DatabasePasswordSecurerBean extends JdbcDaoSupport {
@Autowired
private PasswordEncoder passwordEncoder;
public void secureDatabase() {
getJdbcTemplate().query("select username, password from users",
new RowCallbackHandler(){
@Override
public void processRow(ResultSet rs) throws SQLException {
String username = rs.getString(1);
String password = rs.getString(2);
String encodedPassword =
passwordEncoder.encodePassword(password, null);
getJdbcTemplate().update("update users set password = ?
where username = ?", encodedPassword,username);
logger.debug("Updating password for username:
"+username+" to: "+encodedPassword);
}
});
}
}
The code uses the Spring JdbcTemplate functionality to loop through all the users in the database and encode the password using the injected PasswordEncoder reference. Each password is updated individually.