diff --git a/config/src/main/java/org/springframework/security/config/authentication/UserServiceBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/authentication/UserServiceBeanDefinitionParser.java index d3c8ac13d7..bbd76a73e5 100644 --- a/config/src/main/java/org/springframework/security/config/authentication/UserServiceBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/authentication/UserServiceBeanDefinitionParser.java @@ -9,12 +9,12 @@ import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.PropertiesFactoryBean; import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.ManagedMap; +import org.springframework.beans.factory.support.ManagedList; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.memory.UserMap; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; @@ -37,7 +37,7 @@ public class UserServiceBeanDefinitionParser extends AbstractUserDetailsServiceB private SecureRandom random; protected String getBeanClassName(Element element) { - return "org.springframework.security.core.userdetails.memory.InMemoryDaoImpl"; + return InMemoryUserDetailsManager.class.getName(); } @SuppressWarnings("unchecked") @@ -53,7 +53,7 @@ public class UserServiceBeanDefinitionParser extends AbstractUserDetailsServiceB BeanDefinition bd = new RootBeanDefinition(PropertiesFactoryBean.class); bd.getPropertyValues().addPropertyValue("location", userProperties); - builder.addPropertyValue("userProperties", bd); + builder.addConstructorArgValue(bd); return; } @@ -63,8 +63,7 @@ public class UserServiceBeanDefinitionParser extends AbstractUserDetailsServiceB "properties file (using the '" + ATT_PROPERTIES + "' attribute)" ); } - BeanDefinition userMap = new RootBeanDefinition(UserMap.class); - ManagedMap users = new ManagedMap(); + ManagedList users = new ManagedList(); for (Iterator i = userElts.iterator(); i.hasNext();) { Element userElt = (Element) i.next(); @@ -90,12 +89,10 @@ public class UserServiceBeanDefinitionParser extends AbstractUserDetailsServiceB user.addConstructorArgValue(!locked); user.addConstructorArgValue(authorities.getBeanDefinition()); - users.put(userName, user.getBeanDefinition()); + users.add(user.getBeanDefinition()); } - userMap.getPropertyValues().addPropertyValue("users", users); - - builder.addPropertyValue("userMap", userMap); + builder.addConstructorArgValue(users); } private String generateRandomPassword() { diff --git a/config/src/test/java/org/springframework/security/config/authentication/UserServiceBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/authentication/UserServiceBeanDefinitionParserTests.java index ed77c5c65b..1d55b9f5ac 100644 --- a/config/src/test/java/org/springframework/security/config/authentication/UserServiceBeanDefinitionParserTests.java +++ b/config/src/test/java/org/springframework/security/config/authentication/UserServiceBeanDefinitionParserTests.java @@ -96,7 +96,7 @@ public class UserServiceBeanDefinitionParserTests { UserDetails joe = userService.loadUserByUsername("joe"); assertFalse(joe.isAccountNonLocked()); // Check case-sensitive lookup SEC-1432 - UserDetails bob = userService.loadUserByUsername("bob"); + UserDetails bob = userService.loadUserByUsername("Bob"); assertFalse(bob.isEnabled()); } @@ -107,7 +107,7 @@ public class UserServiceBeanDefinitionParserTests { " " + ""); UserDetailsService userService = (UserDetailsService) appContext.getBean("service"); - userService.loadUserByUsername("joe"); + userService.loadUserByUsername("Joe"); } @Test(expected= FatalBeanException.class) diff --git a/core/src/main/java/org/springframework/security/provisioning/InMemoryUserDetailsManager.java b/core/src/main/java/org/springframework/security/provisioning/InMemoryUserDetailsManager.java new file mode 100644 index 0000000000..cf065bc1f0 --- /dev/null +++ b/core/src/main/java/org/springframework/security/provisioning/InMemoryUserDetailsManager.java @@ -0,0 +1,119 @@ +package org.springframework.security.provisioning; + +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.core.userdetails.memory.UserAttribute; +import org.springframework.security.core.userdetails.memory.UserAttributeEditor; +import org.springframework.util.Assert; + +/** + * Non-persistent implementation of {@code UserDetailsManager} which is backed by an in-memory map. + *

+ * Mainly intended for testing and demonstration purposes, where a full blown persistent system isn't required. + * + * @author Luke Taylor + * @since 3.1 + */ +public class InMemoryUserDetailsManager implements UserDetailsManager { + protected final Log logger = LogFactory.getLog(getClass()); + + private final Map users = new HashMap(); + + private AuthenticationManager authenticationManager; + + public InMemoryUserDetailsManager(Collection users) { + for (UserDetails user : users) { + createUser(user); + } + } + + public InMemoryUserDetailsManager(Properties users) { + Enumeration names = users.propertyNames(); + UserAttributeEditor editor = new UserAttributeEditor(); + + while(names.hasMoreElements()) { + String name = (String) names.nextElement(); + editor.setAsText(users.getProperty(name)); + UserAttribute attr = (UserAttribute) editor.getValue(); + UserDetails user = new User(name, attr.getPassword(), attr.isEnabled(), true, true, true, + attr.getAuthorities()); + createUser(user); + } + } + + public void createUser(UserDetails user) { + Assert.isTrue(!userExists(user.getUsername())); + + users.put(user.getUsername().toLowerCase(), new MutableUser(user)); + } + + public void deleteUser(String username) { + users.remove(username.toLowerCase()); + } + + public void updateUser(UserDetails user) { + Assert.isTrue(userExists(user.getUsername())); + + users.put(user.getUsername().toLowerCase(), new MutableUser(user)); + } + + public boolean userExists(String username) { + return users.containsKey(username.toLowerCase()); + } + + public void changePassword(String oldPassword, String newPassword) { + Authentication currentUser = SecurityContextHolder.getContext().getAuthentication(); + + if (currentUser == null) { + // This would indicate bad coding somewhere + throw new AccessDeniedException("Can't change password as no Authentication object found in context " + + "for current user."); + } + + String username = currentUser.getName(); + + logger.debug("Changing password for user '"+ username + "'"); + + // If an authentication manager has been set, re-authenticate the user with the supplied password. + if (authenticationManager != null) { + logger.debug("Reauthenticating user '"+ username + "' for password change request."); + + authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, oldPassword)); + } else { + logger.debug("No authentication manager set. Password won't be re-checked."); + } + + MutableUserDetails user = users.get(username); + + if (user == null) { + throw new IllegalStateException("Current user doesn't exist in database."); + } + + user.setPassword(newPassword); + } + + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + UserDetails user = users.get(username.toLowerCase()); + + if (user == null) { + throw new UsernameNotFoundException(username); + } + + return user; + } + +} diff --git a/core/src/main/java/org/springframework/security/provisioning/MutableUser.java b/core/src/main/java/org/springframework/security/provisioning/MutableUser.java new file mode 100644 index 0000000000..4b392b633f --- /dev/null +++ b/core/src/main/java/org/springframework/security/provisioning/MutableUser.java @@ -0,0 +1,54 @@ +package org.springframework.security.provisioning; + +import java.util.Collection; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +/** + * + * @author Luke Taylor + * @since 3.1 + */ +class MutableUser implements MutableUserDetails { + private String password; + private final UserDetails delegate; + + public MutableUser(UserDetails user) { + this.delegate = user; + this.password = user.getPassword(); + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Collection getAuthorities() { + return delegate.getAuthorities(); + } + + public String getUsername() { + return delegate.getUsername(); + } + + public boolean isAccountNonExpired() { + return delegate.isAccountNonExpired(); + } + + public boolean isAccountNonLocked() { + return delegate.isAccountNonLocked(); + } + + public boolean isCredentialsNonExpired() { + return delegate.isCredentialsNonExpired(); + } + + public boolean isEnabled() { + return delegate.isEnabled(); + } +} + diff --git a/core/src/main/java/org/springframework/security/provisioning/MutableUserDetails.java b/core/src/main/java/org/springframework/security/provisioning/MutableUserDetails.java new file mode 100644 index 0000000000..317840afd5 --- /dev/null +++ b/core/src/main/java/org/springframework/security/provisioning/MutableUserDetails.java @@ -0,0 +1,14 @@ +package org.springframework.security.provisioning; + +import org.springframework.security.core.userdetails.UserDetails; + +/** + * + * @author Luke Taylor + * @since 3.1 + */ +interface MutableUserDetails extends UserDetails { + + void setPassword(String password); + +}