ItEr38S17ArquitecturaServidorItEr37S07: General refactoring to Spring Security integration and passwords encoded with SHA-2 (SHA-512).

General refactoring to Spring Security integration and passwords econded with SHA-2 (SHA-512). "naval_user" and "user_roles" tables must be removed after applying this patch.
This commit is contained in:
Javier Moran Rua 2009-12-11 13:11:58 +01:00
parent 1345f260fd
commit 03d9834920
11 changed files with 264 additions and 43 deletions

View file

@ -23,8 +23,8 @@ package org.navalplanner.web.users.bootstrap;
import org.navalplanner.business.IDataBootstrap;
/**
* It creates necessary users initially.
* It registers mandatory users in the database (if not registered yet).
*
* @author Fernando Bellas Permuy <fbellas@udc.es>
*/
public interface IUsersBootstrap extends IDataBootstrap {}
public interface IUsersBootstrapInDB extends IDataBootstrap {}

View file

@ -22,20 +22,28 @@ package org.navalplanner.web.users.bootstrap;
import org.navalplanner.business.users.daos.IUserDAO;
import org.navalplanner.business.users.entities.User;
import org.navalplanner.web.users.services.IDBPasswordEncoderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author Fernando Bellas Permuy <fbellas@udc.es>
*/
@Service
@Transactional
public class UsersBootstrap implements IUsersBootstrap {
public class UsersBootstrapInDB implements IUsersBootstrapInDB {
@Autowired
private IUserDAO userDAO;
private IDBPasswordEncoderService dbPasswordEncoderService;
public void setDbPasswordEncoderService(
IDBPasswordEncoderService dbPasswordEncoderService) {
this.dbPasswordEncoderService = dbPasswordEncoderService;
}
@Override
public void loadRequiredData() {
@ -49,11 +57,18 @@ public class UsersBootstrap implements IUsersBootstrap {
if (!userDAO.existsByLoginName(u.getLoginName())) {
userDAO.save(User.create(u.getLoginName(), u.getClearPassword(),
userDAO.save(User.create(u.getLoginName(), getEncodedPassword(u),
u.getInitialRoles()));
}
}
private String getEncodedPassword(MandatoryUser u) {
return dbPasswordEncoderService.encodePassword(u.getClearPassword(),
u.getLoginName());
}
}

View file

@ -0,0 +1,80 @@
/*
* This file is part of ###PROJECT_NAME###
*
* Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e
* Desenvolvemento Tecnolóxico de Galicia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.navalplanner.web.users.services;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.providers.dao.SaltSource;
import org.springframework.security.providers.encoding.PasswordEncoder;
import org.springframework.security.userdetails.User;
import org.springframework.security.userdetails.UserDetails;
/**
* For maximum flexibility, the implementation uses the password encoder and
* the salt source configured in the Spring Security configuration file (in
* consequence, it is possible to change the configuration to use any password
* encoder and/or salt source without modifying the implementation of this
* service). The only restriction the implementation imposes is that when using
* a reflection-based salt source, the "username" property must be specified.
*
* @author Fernando Bellas Permuy <fbellas@udc.es>
*/
public class DBPasswordEncoderService implements IDBPasswordEncoderService {
private SaltSource saltSource;
private PasswordEncoder passwordEncoder;
public void setSaltSource(SaltSource saltSource) {
this.saltSource = saltSource;
}
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
@Override
/**
* The second parameter, <code>loginName</code>, is used as a salt if the
* configured salt source is <code>ReflectionSaltSource</code> (which must
* be configured to use "username" property as a salt).
*/
public String encodePassword(String clearPassword, String loginName) {
/*
* The only important parameter in User's constructor is "loginName",
* which corresponds to the "username" property if the "saltSource" is
* "ReflectionSaltSource". Note that "SystemWideSaltSource" ignores
* the "user" passed as a parameter to "saltSource.getSalt".
*/
UserDetails userDetails = new User(loginName, clearPassword, true,
true, true, true, new GrantedAuthority[0]);
Object salt = null;
if (saltSource != null) {
salt = saltSource.getSalt(userDetails);
}
return passwordEncoder.encodePassword(clearPassword, salt);
}
}

View file

@ -35,7 +35,6 @@ import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UserDetailsService;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
@ -45,8 +44,7 @@ import org.springframework.transaction.annotation.Transactional;
*
* @author Fernando Bellas Permuy <fbellas@udc.es>
*/
@Service("defaultUserDetailsService")
public class DefaultUserDetailsService implements UserDetailsService {
public class DBUserDetailsService implements UserDetailsService {
@Autowired
private IUserDAO userDAO;

View file

@ -23,14 +23,7 @@ package org.navalplanner.web.users.services;
/**
* Service for encoding passwords when information about users
* is stored in the database. In particular, it must be used to encode a
* password when creating a user and to change a user's password. For
* maximum flexibility, the implementation of the service uses the password
* encoder and the salt source configured in the Spring Security configuration
* file (in consequence, it is possible to change the configuration to use
* any password encoder and/or salt source without modifying the
* implementation of this service). The only restriction the implementation
* imposes is that when using a reflection-based salt source, the "username"
* property must be specified.
* password when creating a user and to change a user's password.
* <b/>
* When information about users is maintained externally (e.g. in a LDAP
* server), this service is not used, since the Web application is not
@ -38,6 +31,12 @@ package org.navalplanner.web.users.services;
*
* @author Fernando Bellas Permuy <fbellas@udc.es>
*/
public interface IPasswordEncoderService {
public interface IDBPasswordEncoderService {
/**
* Encodes a clear password. The second parameter (which must be the
* login name) may be used as a salt.
*/
public String encodePassword(String clearPassword, String loginName);
}

View file

@ -3,6 +3,7 @@
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.1.xsd">
@ -28,16 +29,33 @@
</http>
<authentication-provider user-service-ref="defaultUserDetailsService">
<!--
<password-encoder hash="md5">
-->
<!-- NOTE: see IPasswordEncoderService's JavaDoc for restrictions
on "user-property". -->
<!--
<salt-source user-property="username"/>
</password-encoder>
-->
</authentication-provider>
<!--
Beans used by Spring Security (current configuration assumes users
are registered in the database).
-->
<beans:bean id="passwordEncoder" class="org.springframework.security.providers.encoding.ShaPasswordEncoder">
<beans:constructor-arg value="512"/>
</beans:bean>
<beans:bean id="saltSource" class="org.springframework.security.providers.dao.salt.ReflectionSaltSource"
p:userPropertyToUse="username"/>
<beans:bean id="authenticationProvider" class="org.springframework.security.providers.dao.DaoAuthenticationProvider"
p:passwordEncoder-ref="passwordEncoder" p:saltSource-ref="saltSource" p:userDetailsService-ref="dbUserDetailsService">
<custom-authentication-provider/>
</beans:bean>
<!--
Beans used by the Naval Planner Web application when users are
registered in the database. When users are registered externally
(e.g. in a LDAP server), these lines may be commented.
-->
<beans:bean id="dbUserDetailsService" class="org.navalplanner.web.users.services.DBUserDetailsService"/>
<beans:bean id="dbPasswordEncoderService" class="org.navalplanner.web.users.services.DBPasswordEncoderService"
p:passwordEncoder-ref="passwordEncoder" p:saltSource-ref="saltSource"/>
<beans:bean id="usersBootstrapInDB" class="org.navalplanner.web.users.bootstrap.UsersBootstrapInDB"
p:dbPasswordEncoderService-ref="dbPasswordEncoderService"/>
</beans:beans>

View file

@ -24,9 +24,14 @@ package org.navalplanner.web.test;
* A class containing constants for global names.
*
* @author Manuel Rego Casasnovas <mrego@igalia.com>
* @author Fernando Bellas Permuy <fbellas@udc.es>
*/
public class WebappGlobalNames {
public final static String WEBAPP_SPRING_CONFIG_TEST_FILE = "classpath:/navalplanner-webapp-spring-config-test.xml";
public final static String WEBAPP_SPRING_CONFIG_TEST_FILE =
"classpath:/navalplanner-webapp-spring-config-test.xml";
public final static String WEBAPP_SPRING_SECURITY_CONFIG_TEST_FILE =
"classpath:/navalplanner-webapp-spring-security-config-test.xml";
}

View file

@ -24,13 +24,14 @@ import static org.junit.Assert.assertEquals;
import static org.navalplanner.business.BusinessGlobalNames.BUSINESS_SPRING_CONFIG_FILE;
import static org.navalplanner.web.WebappGlobalNames.WEBAPP_SPRING_CONFIG_FILE;
import static org.navalplanner.web.test.WebappGlobalNames.WEBAPP_SPRING_CONFIG_TEST_FILE;
import static org.navalplanner.web.test.WebappGlobalNames.WEBAPP_SPRING_SECURITY_CONFIG_TEST_FILE;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.navalplanner.business.common.exceptions.InstanceNotFoundException;
import org.navalplanner.business.users.daos.IUserDAO;
import org.navalplanner.business.users.entities.User;
import org.navalplanner.web.users.bootstrap.IUsersBootstrap;
import org.navalplanner.web.users.bootstrap.IUsersBootstrapInDB;
import org.navalplanner.web.users.bootstrap.MandatoryUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
@ -38,18 +39,19 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
/**
* Tests for <code>IUsersBootstrap</code>.
* Tests for <code>IUsersBootstrapInDB</code>.
*
* @author Fernando Bellas Permuy <fbellas@udc.es>
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { BUSINESS_SPRING_CONFIG_FILE,
WEBAPP_SPRING_CONFIG_FILE, WEBAPP_SPRING_CONFIG_TEST_FILE })
@ContextConfiguration(locations = {BUSINESS_SPRING_CONFIG_FILE,
WEBAPP_SPRING_CONFIG_FILE, WEBAPP_SPRING_CONFIG_TEST_FILE,
WEBAPP_SPRING_SECURITY_CONFIG_TEST_FILE})
@Transactional
public class UsersBootstrapTest {
public class UsersBootstrapInDBTest {
@Autowired
private IUsersBootstrap usersBootstrap;
private IUsersBootstrapInDB usersBootstrap;
@Autowired
private IUserDAO userDAO;

View file

@ -0,0 +1,80 @@
/*
* This file is part of ###PROJECT_NAME###
*
* Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e
* Desenvolvemento Tecnolóxico de Galicia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.navalplanner.web.test.users.services;
import static org.junit.Assert.assertEquals;
import static org.navalplanner.business.BusinessGlobalNames.BUSINESS_SPRING_CONFIG_FILE;
import static org.navalplanner.web.WebappGlobalNames.WEBAPP_SPRING_CONFIG_FILE;
import static org.navalplanner.web.test.WebappGlobalNames.WEBAPP_SPRING_CONFIG_TEST_FILE;
import static org.navalplanner.web.test.WebappGlobalNames.WEBAPP_SPRING_SECURITY_CONFIG_TEST_FILE;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.navalplanner.business.common.exceptions.InstanceNotFoundException;
import org.navalplanner.business.users.daos.IUserDAO;
import org.navalplanner.business.users.entities.User;
import org.navalplanner.web.users.bootstrap.IUsersBootstrapInDB;
import org.navalplanner.web.users.bootstrap.MandatoryUser;
import org.navalplanner.web.users.services.IDBPasswordEncoderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
/**
* Tests for <code>DBPasswordEncoderService</code>.
*
* @author Fernando Bellas Permuy <fbellas@udc.es>
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {BUSINESS_SPRING_CONFIG_FILE,
WEBAPP_SPRING_CONFIG_FILE, WEBAPP_SPRING_CONFIG_TEST_FILE,
WEBAPP_SPRING_SECURITY_CONFIG_TEST_FILE})
@Transactional
public class DBPasswordEncoderServiceTest {
@Autowired
private IDBPasswordEncoderService dbPasswordEncoderService;
@Autowired
private IUsersBootstrapInDB usersBootstrap;
@Autowired
private IUserDAO userDAO;
@Test
public void testEncodePassword() throws InstanceNotFoundException {
usersBootstrap.loadRequiredData();
for (MandatoryUser u : MandatoryUser.values()) {
String encodedPassword = dbPasswordEncoderService.encodePassword(
u.getClearPassword(), u.getLoginName());
User user = userDAO.findByLoginName(u.getLoginName());
assertEquals(user.getPassword(), encodedPassword);
}
}
}

View file

@ -24,6 +24,7 @@ import static org.junit.Assert.assertEquals;
import static org.navalplanner.business.BusinessGlobalNames.BUSINESS_SPRING_CONFIG_FILE;
import static org.navalplanner.web.WebappGlobalNames.WEBAPP_SPRING_CONFIG_FILE;
import static org.navalplanner.web.test.WebappGlobalNames.WEBAPP_SPRING_CONFIG_TEST_FILE;
import static org.navalplanner.web.test.WebappGlobalNames.WEBAPP_SPRING_SECURITY_CONFIG_TEST_FILE;
import java.util.HashSet;
import java.util.Set;
@ -31,7 +32,7 @@ import java.util.Set;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.navalplanner.business.users.entities.UserRole;
import org.navalplanner.web.users.bootstrap.IUsersBootstrap;
import org.navalplanner.web.users.bootstrap.IUsersBootstrapInDB;
import org.navalplanner.web.users.bootstrap.MandatoryUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.GrantedAuthority;
@ -42,22 +43,22 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
/**
* Tests for implementations of Spring Security's
* <code>UserDetailsService</code>.
* Tests for <code>DBUserDetailsService</code>.
*
* @author Fernando Bellas Permuy <fbellas@udc.es>
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { BUSINESS_SPRING_CONFIG_FILE,
WEBAPP_SPRING_CONFIG_FILE, WEBAPP_SPRING_CONFIG_TEST_FILE })
@ContextConfiguration(locations = {BUSINESS_SPRING_CONFIG_FILE,
WEBAPP_SPRING_CONFIG_FILE, WEBAPP_SPRING_CONFIG_TEST_FILE,
WEBAPP_SPRING_SECURITY_CONFIG_TEST_FILE})
@Transactional
public class UserDetailsServiceTest {
public class DBUserDetailsServiceTest {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private IUsersBootstrap usersBootstrap;
private IUsersBootstrapInDB usersBootstrap;
@Test
public void testLoadUserByUsername() {

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="passwordEncoder" class="org.springframework.security.providers.encoding.ShaPasswordEncoder">
<constructor-arg value="512"/>
</bean>
<bean id="saltSource" class="org.springframework.security.providers.dao.salt.ReflectionSaltSource"
p:userPropertyToUse="username"/>
<bean id="dbUserDetailsService" class="org.navalplanner.web.users.services.DBUserDetailsService"/>
<bean id="dbPasswordEncoderService" class="org.navalplanner.web.users.services.DBPasswordEncoderService"
p:passwordEncoder-ref="passwordEncoder" p:saltSource-ref="saltSource"/>
<bean id="usersBootstrapInDB" class="org.navalplanner.web.users.bootstrap.UsersBootstrapInDB"
p:dbPasswordEncoderService-ref="dbPasswordEncoderService"/>
</beans>