Spring Security 3.1 Password Encoder with Custom Database and JSF 2.0

Posted on Updated on

We have already seen how to use Spring Security and custom database for authentication in earlier post.

But that implementation is saving password as plaintext, which is not an option at all. Both our user admin and guest are essential for application to run, in a usual scenario when an application is deployed, setting up default user and password is a generic company standard based approach.

For our application after we are done with the setup, we will encrypt password using Spring. And we will also update our security configuration to use encrypted password for authentication.

Note: All the pages remain the same, you can check this link for code related to our web pages.

First we will update our jsfspring-sec-security-config.xml to use SHA as the password encoder.

jsfspring-sec-security-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:sec="http://www.springframework.org/schema/security"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xsi:schemaLocation="
	   http://www.springframework.org/schema/beans
	   http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
	   http://www.springframework.org/schema/security
	   http://www.springframework.org/schema/security/spring-security-3.1.xsd">
	   
	 <sec:http auto-config="true" use-expressions="true">
		<sec:intercept-url pattern="/pages/secure/**" access="hasRole('ROLE_USER')" />
		<sec:intercept-url pattern="/pages/unsecure/**" access="permitAll"/>
		<sec:intercept-url pattern="/pages/common/**" access="permitAll"/>
		<sec:intercept-url pattern="/**" access="permitAll"/>
		<sec:form-login login-page="/pages/common/login.jsf"/>
		<sec:remember-me key="jsfspring-sec" services-ref="rememberMeServices"/>
		<sec:logout 
			invalidate-session="true" 
			delete-cookies="JSESSIONID,SPRING_SECURITY_REMEMBER_ME_COOKIE" 
			logout-success-url="/pages/common/login.jsf"></sec:logout>
	</sec:http>
	
	<sec:authentication-manager alias="authenticationManager">
		<sec:authentication-provider ref="rememberMeAuthenticationProvider"></sec:authentication-provider>
		<sec:authentication-provider user-service-ref="customjdbcUserService">
			<sec:password-encoder hash="sha" ref="passwordEncoder"></sec:password-encoder>
		</sec:authentication-provider>
	</sec:authentication-manager>
</beans:beans>

Second we will add the configuration for passwordEncoder and we will also add a bean which will encrypt password for our default user admin and guest, for the very first time when application is run. So let’s update our jsfspring-sec-bean-config.xml

jsfspring-sec-bean-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:sec="http://www.springframework.org/schema/security"
	xsi:schemaLocation="
	   http://www.springframework.org/schema/beans
	   http://www.springframework.org/schema/beans/spring-beans-3.1.xsd 
       http://www.springframework.org/schema/security
	   http://www.springframework.org/schema/security/spring-security-3.1.xsd"
	   >
       
	<beans:bean id="navigator" name="navigator" class="com.mumz.jsfspringsec.beans.Navigator" scope="session">
	</beans:bean>
	
	<beans:bean id="loginBean" name="loginBean" class="com.mumz.jsfspringsec.beans.LoginBean" scope="prototype">
		<beans:property name="authenticationManager" ref="authenticationManager"></beans:property>
		<beans:property name="rememberMeServices" ref="rememberMeServices"></beans:property>
		<beans:property name="userDetailsService" ref="customjdbcUserService"></beans:property>
	</beans:bean>
	
	<beans:bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
	 	<beans:property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<beans:property name="url" value="jdbc:mysql://localhost:3306/jsf-spring-security" />
		<beans:property name="username" value="root" />
		<beans:property name="password" value="root" />
   </beans:bean>
   
   <beans:bean id="customjdbcUserService" class="com.mumz.jsfspringsec.dao.CustomJDBCDaoImpl">
   		<beans:property name="dataSource" ref="dataSource"/>
		<beans:property name="enableAuthorities" value="true"/>
		<beans:property name="usersByUsernameQuery">
			<beans:value>SELECT JSF_SPRING_SEC_USERS_USERNAME, JSF_SPRING_SEC_USERS_PASSWORD, JSF_SPRING_SEC_USERS_ENABLED FROM JSF_SPRING_SEC_USERS WHERE JSF_SPRING_SEC_USERS_USERNAME = ?</beans:value>
		</beans:property>
		<beans:property name="authoritiesByUsernameQuery">
			<beans:value>
				 SELECT JSF_SPRING_SEC_ROLES_USERNAME,JSF_SPRING_SEC_ROLES_ROLE_NAME from JSF_SPRING_SEC_ROLES where JSF_SPRING_SEC_ROLES_USERNAME = ?
			</beans:value>
		</beans:property>
   </beans:bean>
   
	<beans:bean id="rememberMeServices"
		class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
		<beans:property name="key" value="jsfspring-sec" /> 
		<beans:property	name="userDetailsService" ref="customjdbcUserService" /> 
		<beans:property	name="alwaysRemember" value="true" /> 
		<beans:property	name="tokenValiditySeconds" value="60" /> 
	</beans:bean>
	
	<beans:bean id="rememberMeAuthenticationProvider" 
		class="org.springframework.security.authentication.RememberMeAuthenticationProvider">
  		<beans:property name="key" value="jsfspring-sec"/>
	</beans:bean>
	
	<beans:bean id="rememberMeFilter" 
		class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
  		<beans:property name="rememberMeServices" ref="rememberMeServices"/>
  		<beans:property name="authenticationManager" ref="authenticationManager" />
	</beans:bean>
	
	<beans:bean class="org.springframework.security.authentication.encoding.ShaPasswordEncoder" id="passwordEncoder">
	</beans:bean>
	
	<beans:bean id="databasePasswordEncrypter" class="com.mumz.jsfspringsec.dao.security.DBPasswordEncrypterBean" init-method="encryptDBPassword" depends-on="dataSource">
		<beans:property name="passwordEncoder" ref="passwordEncoder"></beans:property>
		<beans:property name="dataSource" ref="dataSource"></beans:property>
		<beans:property name="selectQuery">
			<beans:value>SELECT JSF_SPRING_SEC_USERS_USERNAME, JSF_SPRING_SEC_USERS_PASSWORD, JSF_SPRING_SEC_USERS_ENCRYPTED FROM JSF_SPRING_SEC_USERS WHERE (JSF_SPRING_SEC_USERS_ENCRYPTED='NO' || JSF_SPRING_SEC_USERS_ENCRYPTED IS NULL)</beans:value>
		</beans:property>
		<beans:property name="updateQuery">
			<beans:value>UPDATE JSF_SPRING_SEC_USERS SET JSF_SPRING_SEC_USERS_PASSWORD = ?, JSF_SPRING_SEC_USERS_ENCRYPTED='YES' WHERE JSF_SPRING_SEC_USERS_USERNAME = ? </beans:value>
		</beans:property>
	</beans:bean>
</beans:beans>

We added configuration for passwordEncoder at line 61,62 and then we configured our bean which will encrypt password from line 64-73. Also note in our SQL statement to select users we have added JSF_SPRING_SEC_USERS_ENCRYPTED='NO' || JSF_SPRING_SEC_USERS_ENCRYPTED IS NULL which ensures that password is encrypted only once.

Let’see how our DBPasswordEncrypterBean bean looks like:

DBPasswordEncrypterBean

package com.mumz.jsfspringsec.dao.security;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.security.authentication.encoding.PasswordEncoder;

/**
 * The Class DBPasswordEncrypterBean.
 * @author prabhat.jha
 */
public class DBPasswordEncrypterBean extends JdbcDaoSupport{
	
	/** The Constant selectQuery. */
	private String selectQuery = null;
	
	/** The Constant updateQuery. */
	private String updateQuery = null;
	
	/** The password encoder. */
	private PasswordEncoder passwordEncoder = null;

	/**
	 * Encrypt db password.
	 */
	public void encryptDBPassword(){
		getJdbcTemplate().query(getSelectQuery(), new RowCallbackHandler() {
			@Override
			public void processRow(ResultSet rs) throws SQLException {
				final String username = rs.getString("JSF_SPRING_SEC_USERS_USERNAME");
				final String encryptedPassword = passwordEncoder.encodePassword(rs.getString("JSF_SPRING_SEC_USERS_PASSWORD"), null);
				getJdbcTemplate().update(new PreparedStatementCreator() {
					@Override
					public PreparedStatement createPreparedStatement(Connection con)
							throws SQLException {
						PreparedStatement preparedStatement = con.prepareStatement(getUpdateQuery());
						preparedStatement.setString(1, encryptedPassword);
						preparedStatement.setString(2, username);
						return preparedStatement;
					}
				});
			}
		});
	}
	
	/**
	 * Gets the password encoder.
	 * 
	 * @return the password encoder
	 */
	public PasswordEncoder getPasswordEncoder() {
		return passwordEncoder;
	}

	/**
	 * Sets the password encoder.
	 * 
	 * @param passwordEncoder
	 *            the new password encoder
	 */
	public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
		this.passwordEncoder = passwordEncoder;
	}

	/**
	 * Gets the select query.
	 * 
	 * @return the select query
	 */
	public String getSelectQuery() {
		return selectQuery;
	}

	/**
	 * Sets the select query.
	 * 
	 * @param selectQuery
	 *            the new select query
	 */
	public void setSelectQuery(String selectQuery) {
		this.selectQuery = selectQuery;
	}

	/**
	 * Gets the update query.
	 * 
	 * @return the update query
	 */
	public String getUpdateQuery() {
		return updateQuery;
	}

	/**
	 * Sets the update query.
	 * 
	 * @param updateQuery
	 *            the new update query
	 */
	public void setUpdateQuery(String updateQuery) {
		this.updateQuery = updateQuery;
	}
}

We need to update our JSF_SPRING_SEC_USERS table, below is the create statement.

CREATE TABLE `jsf_spring_sec_users` (
  `PK_JSF_SPRING_SEC_USERS` int(11) NOT NULL AUTO_INCREMENT,
  `JSF_SPRING_SEC_USERS_USERNAME` varchar(45) NOT NULL,
  `JSF_SPRING_SEC_USERS_PASSWORD` varchar(255) NOT NULL,
  `JSF_SPRING_SEC_USERS_ENABLED` varchar(5) NOT NULL,
  `JSF_SPRING_SEC_USERS_CREATED_DT` varchar(45) NOT NULL,
  `JSF_SPRING_SEC_USERS_MODIFIED_DT` varchar(45) DEFAULT NULL,
  `JSF_SPRING_SEC_USERS_ENCRYPTED` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`PK_JSF_SPRING_SEC_USERS`),
  UNIQUE KEY `JSF_SPRING_SEC_USERS_USERNAME_UNIQUE` (`JSF_SPRING_SEC_USERS_USERNAME`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8$$

That’s all we have added encoded password for authentication and also we secured our database.

3 thoughts on “Spring Security 3.1 Password Encoder with Custom Database and JSF 2.0

    human chorionic gonadotropin drops uk said:
    January 16, 2013 at 4:39 AM

    I read this paragraph fully on the topic of the resemblance of latest
    and preceding technologies, it’s awesome article.

    Antonio Lazaro said:
    May 13, 2013 at 6:02 AM

    For me, this class is depracted ” import
    org.springframework.security.authentication.encoding.PasswordEncoder;”
    and there are some errors . I’m using 3.1.4 version of
    spring-security.

    Rashen said:
    April 23, 2015 at 12:55 PM

    I really do have a problem, This is my requirement, thought the above post would cater my requirement yet it seems not.
    1. I need to use a custome login page developed in JSF 2.0, which means an .xhtml page in this scenario.

    2. Wanted to save the encrypted or hashed password in the Database, for an example if the username/password is rashen/333333 then it should not save 333333 in the database, instead it should save an encrypted password, preferbly Bcrypt would do.

    3. I just want to know where exactly spring will convert the normal password to an encrypted ? or is this even possible ?

    4. Do we have to manually check the password? I mean dcrypt and compare or match ?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s