In the last couple of post
- Spring Security 3.1 Adding Salt to Password using Custom Database and JSF 2.0
- Spring Security 3.1 Group-Based Access Control with Custom Database and JSF 2.0
We added salt to our password and implemented Group-Based Access Control. But our salt was dependent on the username
, which is not a very good thing to do. Instead it would be better if some random salt is added. Thankfully Spring Security 3.1 addresses this concern with PasswordEncoder, in this tutorial we will use StandardPasswordEncoder for encoding our password. This also reduces the amount of code that needs to be written.
First we will update our jsfspring-sec-security-config.xml
, we no longer require the saltSource
.
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 ref="passwordEncoder"> </sec:password-encoder> </sec:authentication-provider> </sec:authentication-manager> </beans:beans>
Second databasePasswordEncrypter
, once again there is no need for saltSource
and hence userDetailService
to be injected in databasePasswordEncrypter
, 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="false"/> <beans:property name="enableGroups" value="true"></beans:property> <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:property name="groupAuthoritiesByUsernameQuery"> <beans:value> SELECT GROUPDTLS.JSF_SPRING_SEC_GROUPS_GROUP_ID, GROUPDTLS.JSF_SPRING_SEC_GROUPS_GROUP_NAME, GROUPPERMISSION.JSF_SPRING_SEC_GROUP_AUTHORITIES_AUTHORITY FROM JSF_SPRING_SEC_GROUPS GROUPDTLS, JSF_SPRING_SEC_GROUP_AUTHORITIES GROUPPERMISSION, JSF_SPRING_SEC_GROUP_MEMBERS GROUPMEMBERS, JSF_SPRING_SEC_USERS USERS WHERE USERS.JSF_SPRING_SEC_USERS_USERNAME = ? AND GROUPMEMBERS.JSF_SPRING_SEC_GROUP_MEMBERS_USER_ID = USERS.PK_JSF_SPRING_SEC_USERS AND GROUPMEMBERS.JSF_SPRING_SEC_GROUP_MEMBERS_GROUP_ID = GROUPDTLS.JSF_SPRING_GROUPS_GROUP_ID AND GROUPPERMISSION.JSF_SPRING_SEC_GROUP_AUTHORITIES_GROUP_ID = GROUPDTLS.JSF_SPRING_GROUPS_GROUP_ID </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.crypto.password.StandardPasswordEncoder" 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='' || 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>
Third our DBPasswordEncrypterBean
so that when our application starts, our plaintext password will be hashed with salt.
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.crypto.password.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.encode(rs.getString("JSF_SPRING_SEC_USERS_PASSWORD")); 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; } }
That’s all, remember to clean your database before re-running this example.
springframework BCryptPasswordEncoder is now at
org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder