Jelajahi Sumber

SEC-513: Added support for cache flushing after updating or deleting data in JdbcUserDetailsManager.

Luke Taylor 18 tahun lalu
induk
melakukan
248d97c9d6

+ 21 - 1
core/src/main/java/org/springframework/security/userdetails/jdbc/JdbcUserDetailsManager.java

@@ -6,6 +6,8 @@ import org.springframework.security.AuthenticationException;
 import org.springframework.security.AuthenticationManager;
 import org.springframework.security.context.SecurityContextHolder;
 import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
+import org.springframework.security.providers.dao.UserCache;
+import org.springframework.security.providers.dao.cache.NullUserCache;
 import org.springframework.security.userdetails.UserDetails;
 import org.springframework.security.userdetails.UserDetailsManager;
 import org.springframework.context.ApplicationContextException;
@@ -71,6 +73,8 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa
 
     private AuthenticationManager authenticationManager;
 
+    private UserCache userCache = new NullUserCache();
+
     //~ Methods ========================================================================================================
 
     protected void initDao() throws ApplicationContextException {
@@ -104,11 +108,14 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa
         for (int i=0; i < user.getAuthorities().length; i++) {
             insertAuthority.update(user.getUsername(), user.getAuthorities()[i].getAuthority());
         }
+
+        userCache.removeUserFromCache(user.getUsername());
     }
 
     public void deleteUser(String username) {
         deleteUserAuthorities.update(username);
         deleteUser.update(username);
+        userCache.removeUserFromCache(username);
     }
 
     public void changePassword(String oldPassword, String newPassword) throws AuthenticationException {
@@ -136,6 +143,8 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa
         changePassword.update(new String[] {newPassword, username});
 
         SecurityContextHolder.getContext().setAuthentication(createNewAuthentication(currentUser, newPassword));
+
+        userCache.removeUserFromCache(username);
     }
 
     protected Authentication createNewAuthentication(Authentication currentAuth, String newPassword) {
@@ -195,7 +204,18 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa
     public void setChangePasswordSql(String changePasswordSql) {
         Assert.hasText(changePasswordSql);
         this.changePasswordSql = changePasswordSql;
-    }    
+    }
+
+    /**
+     * Optionally sets the UserCache if one is in use in the application.
+     * This allows the user to be removed from the cache after updates have taken place to avoid stale data.
+     *
+     * @param userCache the cache used by the AuthenticationManager.
+     */
+    public void setUserCache(UserCache userCache) {
+        Assert.notNull(userCache, "userCache cannot be null");
+        this.userCache = userCache;
+    }
 
     //~ Inner Classes ==================================================================================================
 

+ 36 - 2
core/src/test/java/org/springframework/security/userdetails/jdbc/JdbcUserDetailsManagerTests.java

@@ -6,6 +6,7 @@ import org.springframework.security.BadCredentialsException;
 import org.springframework.security.MockAuthenticationManager;
 import org.springframework.security.context.SecurityContextHolder;
 import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
+import org.springframework.security.providers.dao.UserCache;
 import org.springframework.security.userdetails.User;
 import org.springframework.security.userdetails.UserDetails;
 import org.springframework.security.util.AuthorityUtils;
@@ -19,6 +20,9 @@ import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import java.util.Map;
+import java.util.HashMap;
+
 /**
  * Tests for {@link JdbcUserDetailsManager}
  *
@@ -34,6 +38,7 @@ public class JdbcUserDetailsManagerTests {
 
     private static DriverManagerDataSource dataSource;
     private JdbcUserDetailsManager manager;
+    private MockUserCache cache;
     private JdbcTemplate template;
 
     @BeforeClass
@@ -49,6 +54,8 @@ public class JdbcUserDetailsManagerTests {
     @Before
     public void initializeManagerAndCreateTables() {
         manager = new JdbcUserDetailsManager();
+        cache = new MockUserCache();
+        manager.setUserCache(cache);
         manager.setDataSource(dataSource);
         manager.setCreateUserSql(JdbcUserDetailsManager.DEF_CREATE_USER_SQL);
         manager.setUpdateUserSql(JdbcUserDetailsManager.DEF_UPDATE_USER_SQL);
@@ -83,16 +90,17 @@ public class JdbcUserDetailsManagerTests {
     }
 
     @Test
-    public void deleteUserRemovesUserDataAndAuthorities() {
+    public void deleteUserRemovesUserDataAndAuthoritiesAndClearsCache() {
         insertJoe();
         manager.deleteUser("joe");
 
         assertEquals(0, template.queryForList(SELECT_JOE_SQL).size());
         assertEquals(0, template.queryForList(SELECT_JOE_AUTHORITIES_SQL).size());
+        assertFalse(cache.getUserMap().containsKey("joe"));
     }
 
     @Test
-    public void updateUserChangesDataCorrectly() {
+    public void updateUserChangesDataCorrectlyAndClearsCache() {
         insertJoe();
         User newJoe = new User("joe","newpassword",false,true,true,true,
                 AuthorityUtils.stringArrayToAuthorityArray(new String[]{"D","E","F"}));
@@ -102,6 +110,7 @@ public class JdbcUserDetailsManagerTests {
         UserDetails joe = manager.loadUserByUsername("joe");
 
         assertEquals(newJoe, joe);
+        assertFalse(cache.getUserMap().containsKey("joe"));
     }
 
     @Test
@@ -113,6 +122,7 @@ public class JdbcUserDetailsManagerTests {
     public void userExistsReturnsTrueForExistingUsername() {
         insertJoe();
         assertTrue(manager.userExists("joe"));
+        assertTrue(cache.getUserMap().containsKey("joe"));
     }
 
     @Test(expected = AccessDeniedException.class)
@@ -128,6 +138,7 @@ public class JdbcUserDetailsManagerTests {
         UserDetails newJoe = manager.loadUserByUsername("joe");
 
         assertEquals("newPassword", newJoe.getPassword());
+        assertFalse(cache.getUserMap().containsKey("joe"));
     }
 
     @Test
@@ -144,6 +155,7 @@ public class JdbcUserDetailsManagerTests {
         assertEquals("joe", newAuth.getName());
         assertEquals(currentAuth.getDetails(), newAuth.getDetails());
         assertEquals("newPassword", newAuth.getCredentials());
+        assertFalse(cache.getUserMap().containsKey("joe"));
     }
 
     @Test
@@ -162,6 +174,7 @@ public class JdbcUserDetailsManagerTests {
         UserDetails newJoe = manager.loadUserByUsername("joe");
         assertEquals("password", newJoe.getPassword());
         assertEquals("password", SecurityContextHolder.getContext().getAuthentication().getCredentials());
+        assertTrue(cache.getUserMap().containsKey("joe"));
     }
 
     private Authentication authenticateJoe() {
@@ -177,5 +190,26 @@ public class JdbcUserDetailsManagerTests {
         template.execute("insert into authorities (username, authority) values ('joe','A')");
         template.execute("insert into authorities (username, authority) values ('joe','B')");
         template.execute("insert into authorities (username, authority) values ('joe','C')");
+        cache.putUserInCache(joe);
+    }
+
+    private class MockUserCache implements UserCache {
+        private Map cache = new HashMap();
+
+        public UserDetails getUserFromCache(String username) {
+            return (User) cache.get(username);
+        }
+
+        public void putUserInCache(UserDetails user) {
+            cache.put(user.getUsername(), user);
+        }
+
+        public void removeUserFromCache(String username) {
+            cache.remove(username);
+        }
+
+        Map getUserMap() {
+            return cache;
+        }
     }
 }