Jelajahi Sumber

SEC-1464: Created InMemoryUserDetailsManager and converted user-service BDP to use it for its in-memory database.

Luke Taylor 15 tahun lalu
induk
melakukan
f5859fabcf

+ 7 - 10
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<String, BeanDefinition> users = new ManagedMap<String, BeanDefinition>();
+        ManagedList<BeanDefinition> users = new ManagedList<BeanDefinition>();
 
         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() {

+ 2 - 2
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 {
                 "    <user name='joe' password='joespassword' authorities='ROLE_A'/>" +
                 "</user-service>");
         UserDetailsService userService = (UserDetailsService) appContext.getBean("service");
-        userService.loadUserByUsername("joe");
+        userService.loadUserByUsername("Joe");
     }
 
     @Test(expected= FatalBeanException.class)

+ 119 - 0
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.
+ * <p>
+ * 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<String, MutableUserDetails> users = new HashMap<String,MutableUserDetails>();
+
+    private AuthenticationManager authenticationManager;
+
+    public InMemoryUserDetailsManager(Collection<UserDetails> 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;
+    }
+
+}

+ 54 - 0
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<GrantedAuthority> 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();
+    }
+}
+

+ 14 - 0
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);
+
+}