From 248d97c9d640b69f9976b16d00ed3f477f6787ad Mon Sep 17 00:00:00 2001 From: Luke Taylor Date: Mon, 3 Dec 2007 22:12:02 +0000 Subject: [PATCH] SEC-513: Added support for cache flushing after updating or deleting data in JdbcUserDetailsManager. --- .../jdbc/JdbcUserDetailsManager.java | 22 ++++++++++- .../jdbc/JdbcUserDetailsManagerTests.java | 38 ++++++++++++++++++- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/springframework/security/userdetails/jdbc/JdbcUserDetailsManager.java b/core/src/main/java/org/springframework/security/userdetails/jdbc/JdbcUserDetailsManager.java index d9065d1c94..a5ed688b1f 100644 --- a/core/src/main/java/org/springframework/security/userdetails/jdbc/JdbcUserDetailsManager.java +++ b/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 ================================================================================================== diff --git a/core/src/test/java/org/springframework/security/userdetails/jdbc/JdbcUserDetailsManagerTests.java b/core/src/test/java/org/springframework/security/userdetails/jdbc/JdbcUserDetailsManagerTests.java index f63e07c4be..054ce35e03 100644 --- a/core/src/test/java/org/springframework/security/userdetails/jdbc/JdbcUserDetailsManagerTests.java +++ b/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; + } } }