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